在稳定版的Rust中,async fn
不能在trait中使用。目前正在进行的工作将来会使这成为可能,但目前最简单的解决方案是使用async-trait
crate:
#[async_trait]
trait Readable {
async fn readable(&self) -> io::Result<()>;
}
#[async_trait]
impl Readable for Reader {
async fn readable(&self) -> io::Result<()> {
do_stuff().await
}
}
为了避免将Send
绑定放置在async
特质方法上,您可以在特质和实现块上都调用异步特质宏,如#[async_trait(?Send)]
。
#![feature(async_fn_in_trait)]
在夜间版中,现在可以使用async_fn_in_trait
功能编写异步特质方法:
#![feature(async_fn_in_trait)]
trait Readable {
async fn readable(&self) -> io::Result<()>;
}
impl Readable for Reader {
async fn readable(&self) -> io::Result<()> {
do_stuff().await
}
}
然而,目前的实现存在局限性,不允许在返回的future上指定
Send
或
Sync
边界。详情请参见
公告帖子。
关联类型
另一种方法是使用关联类型:
trait Readable {
type Output: Future<Output = io::Result<()>>;
fn readable(&self) -> Self::Output;
}
实现此特质时,您可以使用任何实现了 Future
的类型,例如标准库中的 Ready
:
use std::future;
impl Readable for Reader {
type Output = future::Ready<io::Result<()>>;
fn readable(&self) -> Self::Output {
future::ready(Ok(()))
}
}
async
函数返回一个不透明的impl Future
,因此如果您需要在函数内部调用它,则不能有具体的Output
类型。相反,您可以返回一个动态类型的Future
:
impl Readable for Reader {
type Output = Pin<Box<dyn Future<Output = io::Result<()>>>>;
fn readable(&self) -> Self::Output {
Box::pin(async {
do_stuff().await
})
}
}
请注意,使用这些特质方法将导致每次函数调用都产生堆分配和动态分派,就像使用async-trait crate一样。 对于绝大多数应用程序来说,这不是一个重要的成本,但需要考虑。
捕获引用
可能出现的一个问题是,关联类型Output
没有生命周期,因此无法捕获任何引用:
struct Reader(String);
impl Readable for Reader {
type Output = Pin<Box<dyn Future<Output = io::Result<()>>>>;
fn readable(&self) -> Self::Output {
Box::pin(async move {
println!("{}", self.0);
Ok(())
})
}
}
error: lifetime may not live long enough
--> src/lib.rs:17:9
|
16 | fn readable(&self) -> Self::Output {
| - let's call the lifetime of this reference `'1`
17 | / Box::pin(async move {
18 | | println!("{}", self.0);
19 | | Ok(())
20 | | })
| |__________^ returning this value requires that `'1` must outlive `'static`
在稳定的 Rust 中,关联类型不能具有生命周期,因此您需要将输出限制为一个捕获了 self 的 boxed future,以使这种情况成为可能:
trait Readable {
fn readable(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + '_>>;
}
impl Readable for Reader {
fn readable(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + '_>> {
Box::pin(async move {
println!("{}", self.0);
Ok(())
})
}
}
实际上,这正是async-trait
包在内部执行的操作。
不稳定特性
如果您使用的是夜间版本,则情况会更好。您可以启用type_alias_impl_trait
功能并使用常规的async/await
语法而无需进行包装:
#![feature(type_alias_impl_trait)]
trait Readable {
type Output: Future<Output = io::Result<()>>;
fn readable(&self) -> Self::Output;
}
impl Readable for Reader {
type Output = impl Future<Output = io::Result<()>>;
fn readable(&self) -> Self::Output {
async { ... }
}
}
使用上述代码仍然存在借用问题。但是,由于最近稳定的泛型关联类型功能,您可以将Output
泛型化为生命周期并捕获self
:
trait Readable {
type Output<'a>: Future<Output = io::Result<()>>;
fn readable(&self) -> Self::Output<'_>;
}
而且,前面的例子编译时不会发生装箱操作!
struct Reader(String);
impl Readable for Reader {
type Output<'a> = impl Future<Output = io::Result<()>> + 'a;
fn readable(&self) -> Self::Output<'_> {
Box::pin(async move {
println!("{}", self.0);
Ok(())
})
}
}
async-trait
可以通过在 trait 和实现上使用#[async_trait(?Send)]
来避免Send
约束。 - apetranzilla