wmproxy
已用Rust
實現http/https
代理, socks5
代理, 反向代理, 靜態檔案伺服器,四層TCP/UDP轉發,七層負載均衡,內網穿透,後續將實現websocket
代理等,會將實現過程分享出來,感興趣的可以一起造個輪子
國內: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
trait是Rust
中的概念,類似於其他語言中的介面(interface)
。
在Rust
中不存在繼承的概念,所有關於結構體的拓展功能全部均由trait
來代替。比如std::io::Read
這是一個關於io的trait
,在TcpStream
中和在File
中均實現了該功能,這樣子如果上層只關心讀操作的,我們就可以將其轉化成std::io::Read
的一個物件,比如io: std::io::Read
,後面我們也可以把他包裹成BufferReader
等實現功能的轉化。
下面舉下例子,設計關於車的通用基礎類別,能跑能停等
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
能力,然後摩托車在自行車的基礎上實現Refuel
及RepairEngine
即可實現解耦。
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
。
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()
}
}
當然現在此方法會造成額外的開銷,像Box
,Send
等都會造成一定的效能損失,如果要零損失實現非同步還可以嘗試以下方案
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
在專案中的應用。
點選 [關注],[在看],[點贊] 是對作者最大的支援