0.1.0 |
|
---|
#33 在 #erasure
54KB
871 行
异步特质方法
Rust 1.39中异步/await语言特性的第一轮稳定化没有包括对特质中异步fn的支持。尝试在特质中包含异步fn会产生以下错误
trait MyTrait {
async fn f() {}
}
error[E0706]: trait fns cannot be declared `async`
--> src/main.rs:4:5
|
4 | async fn f() {}
| ^^^^^^^^^^^^^^^
此库提供了一个属性宏,使得特质中的异步fn能够正常工作。
请参考为什么特质中的异步fn很困难,以深入了解此实现与编译器和语言希望在未来交付的内容有何不同。
示例
此示例通过在特质中实现异步fn,使用异步fn实现了一个高度有效的广告平台的核心。
这里需要注意的唯一一点是我们对包含异步fn的特质和特质实现使用#[async_trait]
宏,然后它们就可以正常工作了。
use async_trait::async_trait;
#[async_trait]
trait Advertisement {
async fn run(&self);
}
struct Modal;
#[async_trait]
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,
}
#[async_trait]
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特质的全部功能都能很好地与#[async_trait]一起工作,但是边缘情况很多。如果您看到意外的借用检查器错误、类型错误或警告,请提交一个问题。展开代码中没有使用unsafe
,所以请放心,如果您的代码能够编译,它不会那么糟糕。
- 👍 值、引用、可变引用或无self;
- 👍 任何数量的参数,任何返回值;
- 👍 泛型类型参数和生命周期参数;
- 👍 关联类型;
- 👍 在同一个特质中拥有异步和非异步函数;
- 👍 特质提供的默认实现;
- 👍 省略的生命周期;
- 👍 Dyn兼容的特质。
说明
异步fn被转换为返回Pin<Box<dyn Future + Send + 'async_trait>>
的方法,并委托给一个私有的异步独立函数。
例如上面的impl Advertisement for AutoplayingVideo
会被展开为
impl Advertisement for AutoplayingVideo {
fn run<'async_trait>(
&'async_trait self,
) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'async_trait>>
where
Self: Sync + 'async_trait,
{
async fn run(_self: &AutoplayingVideo) {
/* the original method body */
}
Box::pin(run(self))
}
}
非线程安全 futures
并非所有异步特质的异步方法都需要dyn Future + Send
。为了避免在异步特质方法上放置 Send 和 Sync 的界限,请在特质的宏和 impl 块上都调用#[async_trait(?Send)]
。
省略的生命周期
请注意,异步函数语法不允许在 &
和 &mut
引用之外省略生命周期。即使在未使用 #[async_trait] 的情况下,也是如此。生命周期必须命名或用占位符 '_
标记。
幸运的是,编译器能够通过良好的错误信息诊断缺少的生命周期。
type Elided<'a> = &'a usize;
#[async_trait]
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: `<'_>`
解决方案是命名生命周期或使用 '_
。
#[async_trait]
trait Test {
// either
async fn test<'e>(elided: Elided<'e>) {}
// or
async fn test(elided: Elided<'_>) {}
}
动态特质
具有异步方法的特质可以作为特质对象使用,只要它们满足动态的常规要求——没有类型参数的方法,没有按值传递的 self,没有关联类型等。
#[async_trait]
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,async_trait 宏必须在接收 &self
的特质方法上发出 Self: Sync
的界限,并在接收 &mut self
的特质方法上发出 Self: 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 作为超特质添加,以便约束特质的所有实现者,使默认实现适用于它们
#[async_trait]
pub trait ObjectSafe: Sync { // added supertrait
async fn can_dyn(&self) {}
}
let object = &value as &dyn ObjectSafe;
或者,你可以通过用 Self: Sized
约束来删除特质对象中的问题方法
#[async_trait]
pub trait ObjectSafe {
async fn cannot_dyn(&self) where Self: Sized {}
// presumably other methods
}
let object = &value as &dyn ObjectSafe;
许可证
根据您的选择,在 Apache License, Version 2.0 或 MIT 许可证 下许可。除非您明确声明,否则根据 Apache-2.0 许可证的定义,您有意提交的任何贡献,都将如上所述双重许可,没有任何附加条款或条件。
依赖项
~1.5MB
~36K SLoC