3 个版本
0.2.1 | 2020年10月29日 |
---|---|
0.2.0-samotop-dev | 2020年10月28日 |
1658 在 过程宏 中
每月下载量 114
55KB
769 行
samotop-async-trait 0.2.1
异步特质方法的类型擦除
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 实现了一个高度有效的广告平台的核心。
在这里需要注意的唯一事情是,我们在包含异步函数的特性和特型实现上写了一个#[async_trait]
宏,然后它们就能正常工作了。
use samotop_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
的使用,因此请放心,如果您的代码可以编译,它就不可能太糟糕。
☑ 自身可以通过值、引用、可变引用或没有自身;
☑ 任意数量的参数,任意返回值;
☑ 泛型类型参数和生命周期参数;
☑ 关联类型;
☑ 在同一特型中拥有异步和非异步函数;
☑ 特型提供的默认实现;
☑ 省略的生命周期;
☑ 具有动态能力的特型。
☑ 通过#[future_is[BOUND]]选择更严格的futures界限。
说明
异步函数被转换成返回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 core::future::Future<Output = ()> + Send + 'async_trait>>
where
Self: Sync + 'async_trait,
{
let fut = async move {
/* the original method body */
};
Box::pin(fut)
}
}
非线程安全的futures
并非所有的异步特型都需要dyn Future + Send
的futures。为了避免在异步特型方法上放置Send和Sync界限,请在特型和实现块上同时调用异步特型宏为#[async_trait(?Send)]
。
省略的生命周期
请注意,异步函数语法不允许在&
和&mut
引用之外省略生命周期。(即使不使用#[async_trait],这也是正确的。)生命周期必须命名或用占位符'_
标记。
幸运的是,编译器能够通过良好的错误信息诊断缺失的生命周期。
# use samotop_async_trait::async_trait;
#
type Elided<'a> = &'a usize;
#[async_trait]
trait Test {
async fn test(elided: Elided, okay: &usize) -> &usize { elided }
}
error[E0106]: missing lifetime specifier
|
19 | async fn test(elided: Elided, okay: &usize) -> &usize { elided }
| ------ ------ ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `elided` or `okay`
note: these named lifetimes are available to use
|
17 | #[async_trait]
| ^^^^^^^^^^^^^^
修复方法是命名生命周期。
#[async_trait]
trait Test {
// either
async fn test<'e>(elided: Elided<'e>, okay: &usize) -> &'e usize { 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作为超特性(如果存在具有默认实现的&mut self方法,则添加Send;如果存在具有默认实现的self方法,则添加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;
使用#[future_is[BOUND]]更严格的future约束
你可以要求future是Sync或'static,甚至两者都要
#[async_trait]
trait SyncStaticFutures {
#[future_is[Sync + 'static]]
async fn sync_and_static(&self) -> String;
}
fn test<T:SyncStaticFutures>(tested:T)
{
is_sync(tested.sync_and_static());
is_static(tested.sync_and_static());
}
fn is_sync<T: Sync>(_tester: T) {}
fn is_static<T: 'static>(_tester: T) {}
实现静态future的问题在于,一旦异步块捕获了对self的引用,例如,异步块就不能是静态的。解决方案:在异步块外部使用async_setup_ready!宏来执行future的设置
#[async_trait]
trait SyncStaticFutures {
#[future_is[Sync + 'static]]
async fn sync_and_static(&self) -> String;
}
struct Dummy{
message: String
}
#[async_trait]
impl SyncStaticFutures for Dummy {
#[future_is[Sync + 'static]]
async fn sync_and_static(&self) -> String
{
let msg = self.message.clone();
let msg = async move {msg}; // aka future::ready(msg)
async_setup_ready!();
// your async/await business here...
// for instance reading the string from the given file path
msg.await
}
}
致谢
这是dtolnay的async-trait的分支
许可证 - MIT OR Apache-2.0
根据您的选择,在以下任一许可证下授权:Apache License, Version 2.0或MIT许可证。除非您明确声明,否则根据Apache-2.0许可证定义的,您有意提交以包含在此crate中的任何贡献,都将根据上述许可证双授权,没有额外的条款或条件。
依赖项
~1.5MB
~35K SLoC