29. 乾貨系列從零用Rust編寫正反向代理,非同步回調(async trait)的使用

来源:https://www.cnblogs.com/wmproxy/archive/2023/11/21/wmproxy29.html
-Advertisement-
Play Games

回調賦予我們很好的函數處理能力,那麼非同步回調則是在非同步編程中的靈魂,這是不可缺少的存在,本章主要是關於Rust中的trait的說明,希望對你有所幫助 ...


wmproxy

wmproxy已用Rust實現http/https代理, socks5代理, 反向代理, 靜態文件伺服器,四層TCP/UDP轉發,七層負載均衡,內網穿透,後續將實現websocket代理等,會將實現過程分享出來,感興趣的可以一起造個輪子

項目地址

國內: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

關於 ++trait++

  trait是Rust中的概念,類似於其他語言中的介面(interface)
  在Rust中不存在繼承的概念,所有關於結構體的拓展功能全部均由trait來代替。比如std::io::Read這是一個關於io的trait,在TcpStream中和在File中均實現了該功能,這樣子如果上層只關心讀操作的,我們就可以將其轉化成std::io::Read的一個對象,比如io: std::io::Read,後面我們也可以把他包裹成BufferReader等實現功能的轉化。

為什麼不在Rust中使用繼承

  • 繼承破壞了封裝性,父類的改變會影響子類。如果父類的出現發生變化,所有繼承自該父類的子類都需要相應地進行修改,這會增加代碼的維護成本。
  • Rust追求記憶體安全和無數據競爭,繼承不利於編譯器進行靜態檢查。
  • 繼承關係的耦合度高。子類和父類之間是緊密耦合的關係,這會影響代碼的靈活性和可移植性。
  • 繼承往往被過度使用,導致子類與父類功能緊密耦合。

下麵舉下例子,設計關於車的通用基類,能跑能停等

public class BaseCar { 
  //... 省略其他屬性和方法... 
  public void run() { //... }
  public void stop() { //... }
}

一開始自行車都很完美,接下來設計摩托車,摩托車需要加油,那麼基類被改成

public class BaseCar { 
  //... 省略其他屬性和方法... 
  public void run() { //... }
  public void stop() { //... }
  public void refuel() { //... }
}

但是自行車又沒有加油的需求

// 自行車
public class Bicycle extends BaseCar { 
 //... 省略其他屬性和方法... 
 public void refuel() { 
     throw new UnSupportedMethodException("我不需要加油!");
 }
}

如果接下來又有修理引擎的介面,那基類又得加repairEngine的介面。自行車繼承這個基類將會產生嚴重的負擔,不繼承又得重新寫一些關於基礎能力的函數,又會增加重覆代碼。

那麼接下來是以trait方案的實現

pub trait Base {
   fn run(&self); 
   fn stop(&self); 
}
pub trait Refuel {
   fn refuel(&mut self); 
}
pub trait RepairEngine {
   fn repair_engine(&mut self); 
}

那麼自行車只需要實現Base能力,然後摩托車在自行車的基礎上實現RefuelRepairEngine即可實現解耦。

非同步的trait

在程式中均使用的是非同步(async)編程,那麼我們可能需要將trait實現成:

pub trait Base {
   async fn run(&self); 
   async fn stop(&self); 
}

當我們如此寫的時候編譯器就會提示我們:

functions in traits cannot be declared `async`
`async` trait functions are not currently supported
consider using the `async-trait` crate: https://crates.io/crates/async-trait
see issue #91611 <https://github.com/rust-lang/rust/issues/91611> for more informationrustcClick for full compiler diagnostic

原來非同步的trait實現還沒有進入到stable階段,暫時只能有預覽版即nightly版本進行使用。

那麼本文將探討該功能在未stable前如何實現非同步的trait

假如返回一個非同步的Future

trait Base {
    type FetchData<'a>: std::future::Future<Output = String> + 'a where Self: 'a;
    fn run<'a>(&'a self) -> Self::FetchData<'a>;
}

那麼實現自行車的函數將為:

trait Base {
    type FetchData<'a>: /* 將要何種類型呢?? */;
    fn run<'a>(&'a self) -> Self::FetchData<'a>;
}

我們嘗試過各種類型,編譯器都無法通過編譯,所以我們需要進行返回值的修改,我們將通過運行時類型擦除來實現。

首先,我們可以通過用 擦除 future 類型來避免編寫 future 類型。以上面的例子為例,你可以這樣寫你的特征:dyn

trait Base {
    fn run<'a>(&'a self) -> Pin<Box<dyn Future<Output = String> + Send + '_>>;
}

那麼實現將為:

impl Base for Bicycle {
    fn run<'a>(&'a self) -> std::pin::Pin<Box<dyn std::future::Future<Output = String> + Send + '_>> {
        Box::pin(async {
            "ok".to_string()
        })
    }
}

可以看出整個函數非常的冗餘,相當的讓人難受。

那麼此時我們可以藉助async-trait的巨集處理庫,他將幫我們自動處理掉無用的數據,那麼我們的代碼將變成如下:

#[async_trait]
trait Base {
    async fn run(&self) -> String;
}
#[async_trait]
impl Base for Bicycle {
    async fn run(&self) -> String {
        "ok".to_string()
    }
}

當然現在此方法會造成額外的開銷,像BoxSend等都會造成一定的性能損失,如果要零損失實現非同步還可以嘗試以下方案

手動實現Poll

需要零開銷或在no_std上下文中工作的特征還有另一種選擇:它們可以從 Future 特征中獲取輪詢的概念,並將其直接構建到它們的界面中。如果 future 已完成,並且 future 正在等待其他事件,則該方法將返回。Future::poll,Poll::Ready(Output),Poll::Pending

pub trait Base {
    type Item;
    fn poll_next(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>
    ) -> Poll<Option<Self::Item>>;
}

當然控制Poll的方式相當的麻煩,只要在對性能要求極高的情況下在進行此操作。

預期的官方實現

在最新的Beta或者nightly版本中可以用#![feature(async_fn_in_trait)]來啟用該能力,那麼我們就可以如下編程:

#![feature(async_fn_in_trait)]

trait Base {
    async fn run(&self) -> String;
}

impl Base for Bicycle {
    async fn run(&self) -> String {
        "ok".to_string()
    }
}

這樣子就和普通的實現沒有什麼差別了。

實現該功能的難點

理論上來說,一個非同步只有你在調用await的時候他才會真正的被調用,如果在此前有引用對話的存在,那麼他的生命周期管理才是比較麻煩的存在。

小結

當前的Rust版本為1.74.0,好消息的是當前async trait已經Beta Channel了,如果不出意外的話下一次發佈版本的穩定版將會擁有該能力了。該功能的官方實現將會給非同步編程的帶來極大的方便。讓async/await能力越來越強。預期2023年末就可以直接使用了。下一章節我們將講async trait在項目中的應用。

點擊 [關註][在看][點贊] 是對作者最大的支持


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 題目: 給你兩個按 非遞減順序 排列的整數數組 nums1 和 nums2,另有兩個整數 m 和 n ,分別表示 nums1 和 nums2 中的元素數目。 請你 合併 nums2 到 nums1 中,使合併後的數組同樣按 非遞減順序 排列。 註意:最終,合併後數組不應由函數返回,而是存儲在數組 n ...
  • AcWing 演算法基礎課week 1 總結 總結點 1:快速排序(分治思想) 題1:從小到大排序 主體思路:定義一個數x屬於數組s,利用雙指針,將數組分為大於等於x和小於等於x的兩部分,然後遞歸處理。(具體步驟如下) 1. 如上圖所示,我們定義一個數組s用來儲存n個數據,然後定義兩個指針i j,分別 ...
  • Assistants介紹 隨著OpenAI將Assistants助手API對外發佈,我們搭建個人知識庫變的如此簡單。開發者將自己的應用通過Assistants API與OpenAI對接,就可以讓每一位客戶擁有不一般體驗的個人知識庫。由於Assistants相關API有30+,本文只列舉完成一個最小功 ...
  • 事務是保證業務操作完整性的一種資料庫機制,具有原子性、一致性、隔離性和持久性(ACID)的特點。 在Java中,可以通過JDBC和MyBatis來控制事務,底層都是通過Connection對象完成的。 Spring使用AOP的方式進行事務開發,通過將事務的額外功能封裝在DataSourceTrans... ...
  • 事件緣起我在Linux伺服器(CentOS 7.8)安裝Python3.10,並替換python軟鏈接為python3之後,yum命令不可用。特此記錄一下。 完整安裝步驟如下: Python3.10安裝 1.使用yum程式提前安裝Python依賴。 yum install wget zlib-dev ...
  • 作者:FishBones 鏈接:https://juejin.cn/post/7185479136599769125 背景 公司的一個ToB系統,因為客戶使用的也不多,沒啥併發要求,就一直沒有經過壓測。這兩天來了一個“大客戶”,對併發量提出了要求:核心介面與幾個重點使用場景單節點吞吐量要滿足最低50 ...
  • 在本篇文章中,我們總結了Python中的異常捕獲的重要性以及如何進行優化。異常捕獲是一種處理程式在執行過程中出現錯誤的機制,對於程式的穩定性和可靠性至關重要。我們詳細學習了Python中的基本異常捕獲語法,包括try、except、else和finally塊,並舉例了常見的異常類型,總之,閱讀本文只... ...
  • scipy.signal模塊主要用於處理和分析信號。它提供了大量的函數和方法,用於濾波、捲積、傅里葉變換、雜訊生成、周期檢測、譜分析等信號處理任務。 此模塊的主要作用是提供一套完整的信號處理工具,從而幫助用戶對各種連續或者離散的時間序列數據、音頻信號、電信號或其他物理信號進行操作和分析。它支持許多標 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...