1个不稳定版本
0.1.68 | 2023年2月2日 |
---|
#1753 在 异步
每月446次下载
53KB
862 行
异步特性方法
这是一个异步特性的定制实现,除了async_trait
导出async_trait_with_send
和async_trait_without_send
属性宏之外。这允许使用async_trait(?Send)
而无需?Send
限定符,从而允许使用#cfg[]
条件语句来根据功能或平台架构在Send
和?Send
要求之间进行切换。
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() {}
| ^^^^^^^^^^^^^^^
此crate提供了一个属性宏,使特性行异步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能力的特质。
说明
异步函数被转换成返回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))
}
}
非线程安全的future
并非所有异步特质都需要dyn Future + Send
类型的future。为了避免在异步特质的函数上放置Send和Sync约束,请在特质的声明和实现块上调用异步特质宏,使用#[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<'_>) {}
}
Dyn特质
具有异步方法的特质可以作为特质对象使用,只要它们满足dyn的常规要求——没有类型参数的方法、没有按值传递的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
有一个小问题是在提供异步方法默认实现的特质中。为了使默认实现生成的future是Send的,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作为超特征(如果存在具有默认实现的&mut self
方法,则为Sync,如果存在具有默认实现的&self
方法),以约束所有实现该特征的开发者,使得默认实现适用于他们
#[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许可证的定义,您有意提交的任何贡献,包括在本crate中包含的贡献,都将按照上述方式双重许可,不附加任何额外条款或条件。
依赖项
~1.5MB
~36K SLoC