编辑
2024-01-19
网络
00
请注意,本文编写于 296 天前,最后修改于 296 天前,其中某些信息可能已经过时。

目录

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追求内存安全和无数据竞争,继承不利于编译器进行静态检查。
  • 继承关系的耦合度高。子类和父类之间是紧密耦合的关系,这会影响代码的灵活性和可移植性。
  • 继承往往被过度使用,导致子类与父类功能紧密耦合。

下面举下例子,设计关于车的通用基类,能跑能停等

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

一开始自行车都很完美,接下来设计摩托车,摩托车需要加油,那么基类被改成

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

但是自行车又没有加油的需求

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

如果接下来又有修理引擎的接口,那基类又得加repairEngine的接口。自行车继承这个基类将会产生严重的负担,不继承又得重新写一些关于基础能力的函数,又会增加重复代码。

那么接下来是以trait方案的实现

RUST
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实现成:

rust
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

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

那么实现自行车的函数将为:

RUST
trait Base { type FetchData<'a>: /* 将要何种类型呢?? */; fn run<'a>(&'a self) -> Self::FetchData<'a>; }

我们尝试过各种类型,编译器都无法通过编译,所以我们需要进行返回值的修改,我们将通过运行时类型擦除来实现。

首先,我们可以通过用 擦除 future 类型来避免编写 future 类型。以上面的例子为例,你可以这样写你的特征:dyn

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

那么实现将为:

RUST
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的宏处理库,他将帮我们自动处理掉无用的数据,那么我们的代码将变成如下:

RUST
#[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

RUST
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)]来启用该能力,那么我们就可以如下编程:

RUST
#![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在项目中的应用。

点击 [关注][在看][点赞] 是对作者最大的支持

本文作者:问蒙服务框架

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!