#async-trait #traits #methods #erasure #lifetime

trait-async

异步特性方法的类型擦除

4个版本

0.1.24 2020年3月5日
0.1.2 2020年3月5日
0.1.1 2020年3月5日
0.1.0 2020年3月5日

#30 in #erasure

Download history 75/week @ 2024-03-31 62/week @ 2024-04-07 64/week @ 2024-04-14 105/week @ 2024-04-21 79/week @ 2024-04-28 51/week @ 2024-05-05 55/week @ 2024-05-12 54/week @ 2024-05-19 56/week @ 2024-05-26 57/week @ 2024-06-02 43/week @ 2024-06-09 99/week @ 2024-06-16 101/week @ 2024-06-23 68/week @ 2024-06-30 69/week @ 2024-07-07 56/week @ 2024-07-14

每月306次下载
用于 3 个crate(直接使用2个)

MIT/Apache

45KB
733

异步特性方法

Build Status Latest Version Rust Documentation

异步/await语言特性预计将在Rust 1.39(跟踪问题:[rust-lang/rust#62149](https://github.com/rust-lang/rust/issues/62149))的第一个稳定版本中实现,但这不包括对特性中异步fn的支持。尝试在特性中包含异步fn将产生以下错误

trait MyTrait {
    async fn f() {}
}
error[E0706]: trait fns cannot be declared `async`
 --> src/main.rs:4:5
  |
4 |     async fn f() {}
  |     ^^^^^^^^^^^^^^^

本crate提供了一个属性宏,使特性中的异步fn能够工作。

请参阅[为什么特性中的异步fn如此困难](https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/),以深入了解这种实现与编译器和语言希望在未来提供的内容有何不同。


示例

此示例通过在特性中使用异步fn实现了高度有效的广告平台的核心。

这里唯一需要注意的是,我们在包含异步fn的特性及其实现上使用了一个#[trait_async]宏,然后它们就可以工作了。

use trait_async::trait_async;

#[trait_async]
trait Advertisement {
    async fn run(&self);
}

struct Modal;

#[trait_async]
impl Advertisement for Modal {
    async fn run(&self) {
        self.render_fullscreen().await;
        for _ in 0..4u16 {
            remind_user_to_join_mailing_list().await;
        }
        self.hide_for_now().await;
    }
}

struct AutoplayingVideo {
    media_url: String,
}

#[trait_async]
impl Advertisement for AutoplayingVideo {
    async fn run(&self) {
        let stream = connect(&self.media_url).await;
        stream.play().await;

        // Video probably persuaded user to join our mailing list!
        Modal.run().await;
    }
}

支持的功能

目标是让Rust特性的所有功能都能与#[trait_async]很好地工作,但边缘情况众多。如果看到意外的借用检查器错误、类型错误或警告,请提交一个问题。扩展代码中没有使用unsafe,所以请放心,如果代码可以编译,它就不太可能严重损坏。

  • 👍 按值、按引用、按可变引用或无self;
  • 👍 任何数量的参数,任何返回值;
  • 👍 泛型类型参数和生命周期参数;
  • 👍 关联类型;
  • 👍 在同一个特性中具有异步和非异步函数;
  • 👍 特性提供的默认实现;
  • 👍 省略的生命周期;
  • 👍 支持Dyn的特性。

说明

异步fn被转换为返回Pin<Box<dyn Future + Send + 'async>>的方法,并将委托给一个私有的异步独立函数。

例如上面的impl Advertisement for AutoplayingVideo会被扩展为

impl Advertisement for AutoplayingVideo {
    fn run<'async>(
        &'async self,
    ) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'async>>
    where
        Self: Sync + 'async,
    {
        async fn run(_self: &AutoplayingVideo) {
            /* the original method body */
        }

        Box::pin(run(self))
    }
}

非线程安全未来

并非所有异步特性都需要是dyn Future + Send类型。为了避免在异步特性方法上放置Send和Sync边界,请在特性和实现块上都使用#[trait_async(?Send)]宏。


省略的生命周期

请注意,异步函数语法不允许在&&mut引用之外省略生命周期。(即使不使用#[trait_async],这也同样适用。)生命周期必须命名或由占位符'_标记。

幸运的是,编译器能够通过良好的错误消息诊断出缺失的生命周期。

type Elided<'a> = &'a usize;

#[trait_async]
trait Test {
    async fn test(not_okay: Elided, okay: &usize) {}
}
error[E0726]: implicit elided lifetime not allowed here
 --> src/main.rs:9:29
  |
9 |     async fn test(not_okay: Elided, okay: &usize) {}
  |                             ^^^^^^- help: indicate the anonymous lifetime: `<'_>`

修复方法是命名生命周期或使用'_

#[trait_async]
trait Test {
    // either
    async fn test<'e>(elided: Elided<'e>) {}
    // or
    async fn test(elided: Elided<'_>) {}
}

动态特性

具有异步方法的特性可以作为特性对象使用,只要它们满足动态特性通常的要求——没有类型参数的方法,没有按值传递的self,没有关联类型等。

#[trait_async]
pub trait ObjectSafe {
    async fn f(&self);
    async fn g(&mut self);
}

impl ObjectSafe for MyType {...}

let value: MyType = ...;
let object = &value as &dyn ObjectSafe;  // make trait object

一个问题在于提供异步方法默认实现的特性。为了使默认实现产生一个Send类型的future,异步特性宏必须在需要&self的特性和需要&mut self的特性能产生Send类型的future上,分别添加Self: SyncSelf: Send约束。上述解释部分中的展开代码中可以看到前者的示例。

如果你创建了一个具有默认实现的异步方法特性的特性,一切都将正常工作,但该特性不能用作特性对象。创建类型为&dyn Trait的值将产生一个错误,类似于以下内容

error: the trait `Test` cannot be made into an object
 --> src/main.rs:8:5
  |
8 |     async fn cannot_dyn(&self) {}
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

对于需要作为对象安全且需要对某些异步方法提供默认实现的特性,有两种解决方案。或者,你可以将Send和/或Sync作为超特性添加到特性中,以便约束所有特性的实现者,使得默认实现适用于它们

#[trait_async]
pub trait ObjectSafe: Sync {  // added supertrait
    async fn can_dyn(&self) {}
}

let object = &value as &dyn ObjectSafe;

或者,你可以通过将它们与Self: Sized约束来从特性对象中删除有问题的方法

#[trait_async]
pub trait ObjectSafe {
    async fn cannot_dyn(&self) where Self: Sized {}

    // presumably other methods
}

let object = &value as &dyn ObjectSafe;

许可证

根据您的选择,许可协议为Apache许可证,版本2.0MIT许可证
除非您明确声明,否则根据Apache-2.0许可证定义的,您有意提交以包含在此包中的任何贡献,都将按照上述方式双重许可,而无需任何额外的条款或条件。

依赖项

~1.5MB
~35K SLoC