如何在特征中定义异步方法?
How can I define an async method in a trait?
我有一个特征,我用它来抽象掉 tokio::net::TcpStream
和 tokio::net::UnixStream
:
/// Interface for TcpStream and UnixStream.
trait TryRead {
// overlapping the name makes it hard to work with
fn do_try_read(&self, buf: &mut [u8]) -> Result<usize, std::io::Error>;
}
impl TryRead for TcpStream {
fn do_try_read(&self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
self.try_read(buf)
}
}
问题是我想在两种方法中抽象掉 pub async fn readable(&self) -> io::Result<()>
但异步方法不能在特征中实现。我该如何处理?
目前无法使用async
methods in traits。虽然该功能已经稳定(并且可能需要相当长的时间),但我知道的唯一解决方案是 async_trait
crate。
use async_trait::async_trait;
#[async_trait]
trait Readable {
fn async readable(&self) -> io::Result<()>;
}
async_trait
宏基本上只是将您的函数转换为以下代码:
trait Readable {
fn readable<'a>(&self) -> Pin<Box<dyn 'a + Send + Future<Output = io::Result<()>>>
}
此方法的缺点是特征对象的额外成本。
目前,async fn
不能用于特征。这样做的原因有些复杂,但有计划在未来取消这一限制。可以参考why async fn in traits are hard深入分析问题
关联类型
同时,您可以使用关联类型:
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
函数 return 一个不透明的 impl Future
,所以如果你需要调用一个,你没有具体的类型来设置 Output
。相反,您可以 return 一个动态类型的 Future
:
impl Readable for Reader {
// or use the handy type alias from the futures crate:
// futures::BoxFuture<'static, io::Result<()>>
type Output = Pin<Box<dyn Future<Output = io::Result<()>>>>;
fn readable(&self) -> Self::Output {
let fut = async {
do_stuff().await
};
Box::pin(fut)
}
}
请注意,使用这些特征方法将导致每个函数调用进行堆分配和动态分派。对于绝大多数应用程序来说,这不是一个很大的成本,但需要考虑。
捕获引用
可能出现的一个问题是关联类型 Output
没有生命周期,因此无法捕获任何引用:
struct Reader(String);
impl Readable for Reader {
type Output = Pin<Box<dyn Future<Output = io::Result<()>>>>;
fn readable(&self) -> Self::Output {
let fut = async move {
println!("{}", self.0);
Ok(())
};
Box::pin(fut)
}
}
error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
--> src/lib.rs:17:30
|
16 | fn readable(&self) -> Self::Output {
| ----- this data with an anonymous lifetime `'_`...
17 | let fut = async move {
| ______________________________^
18 | | println!("{}", self.0);
19 | | Ok(())
20 | | };
| |_________^ ...is captured here...
21 | Box::pin(fut)
| ------------- ...and is required to live as long as `'static` here
稳定 Rust 上的关联类型不能有生命周期,因此您必须将输出限制为从 self 捕获的盒装未来,以实现此目的:
trait Readable {
// note the anonymous lifetime ('_) that refers to &self
fn readable(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + '_>>;
}
impl Readable for Reader {
fn readable(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + '_>> {
let fut = async move {
println!("{}", self.0);
Ok(())
};
Box::pin(fut)
}
}
async_trait
要避免一些样板文件,您可以使用 async-trait
crate:
#[async_trait]
trait Readable {
fn async readable(&self) -> io::Result<()>;
}
#[async_trait]
impl Readable for Reader {
async fn readable(&self) -> io::Result<()> {
do_stuff().await
}
}
async-trait
将async
方法转化为returnPin<Box<dyn Future<Output = ...> + Send = '_>>
的方法,和我们之前写的类似,所以也要考虑到和上面一样的地方。
为了避免将 Send
绑定放置在 async
trait 方法上,您可以在 trait 和 impl 块上调用异步 trait 宏作为 #[async_trait(?Send)]
。
不稳定的功能
如果你在夜间,故事会更好。您可以启用 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 { ... }
}
}
借用问题仍然适用于上述代码。但是,使用不稳定的特性 generic_associated_types
,您可以在整个生命周期内使 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<'_> {
let fut = async move {
println!("{}", self.0); // we can capture self!
Ok(())
};
Box::pin(fut)
}
}
我有一个特征,我用它来抽象掉 tokio::net::TcpStream
和 tokio::net::UnixStream
:
/// Interface for TcpStream and UnixStream.
trait TryRead {
// overlapping the name makes it hard to work with
fn do_try_read(&self, buf: &mut [u8]) -> Result<usize, std::io::Error>;
}
impl TryRead for TcpStream {
fn do_try_read(&self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
self.try_read(buf)
}
}
问题是我想在两种方法中抽象掉 pub async fn readable(&self) -> io::Result<()>
但异步方法不能在特征中实现。我该如何处理?
目前无法使用async
methods in traits。虽然该功能已经稳定(并且可能需要相当长的时间),但我知道的唯一解决方案是 async_trait
crate。
use async_trait::async_trait;
#[async_trait]
trait Readable {
fn async readable(&self) -> io::Result<()>;
}
async_trait
宏基本上只是将您的函数转换为以下代码:
trait Readable {
fn readable<'a>(&self) -> Pin<Box<dyn 'a + Send + Future<Output = io::Result<()>>>
}
此方法的缺点是特征对象的额外成本。
目前,async fn
不能用于特征。这样做的原因有些复杂,但有计划在未来取消这一限制。可以参考why async fn in traits are hard深入分析问题
关联类型
同时,您可以使用关联类型:
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
函数 return 一个不透明的 impl Future
,所以如果你需要调用一个,你没有具体的类型来设置 Output
。相反,您可以 return 一个动态类型的 Future
:
impl Readable for Reader {
// or use the handy type alias from the futures crate:
// futures::BoxFuture<'static, io::Result<()>>
type Output = Pin<Box<dyn Future<Output = io::Result<()>>>>;
fn readable(&self) -> Self::Output {
let fut = async {
do_stuff().await
};
Box::pin(fut)
}
}
请注意,使用这些特征方法将导致每个函数调用进行堆分配和动态分派。对于绝大多数应用程序来说,这不是一个很大的成本,但需要考虑。
捕获引用
可能出现的一个问题是关联类型 Output
没有生命周期,因此无法捕获任何引用:
struct Reader(String);
impl Readable for Reader {
type Output = Pin<Box<dyn Future<Output = io::Result<()>>>>;
fn readable(&self) -> Self::Output {
let fut = async move {
println!("{}", self.0);
Ok(())
};
Box::pin(fut)
}
}
error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
--> src/lib.rs:17:30
|
16 | fn readable(&self) -> Self::Output {
| ----- this data with an anonymous lifetime `'_`...
17 | let fut = async move {
| ______________________________^
18 | | println!("{}", self.0);
19 | | Ok(())
20 | | };
| |_________^ ...is captured here...
21 | Box::pin(fut)
| ------------- ...and is required to live as long as `'static` here
稳定 Rust 上的关联类型不能有生命周期,因此您必须将输出限制为从 self 捕获的盒装未来,以实现此目的:
trait Readable {
// note the anonymous lifetime ('_) that refers to &self
fn readable(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + '_>>;
}
impl Readable for Reader {
fn readable(&self) -> Pin<Box<dyn Future<Output = io::Result<()>> + '_>> {
let fut = async move {
println!("{}", self.0);
Ok(())
};
Box::pin(fut)
}
}
async_trait
要避免一些样板文件,您可以使用 async-trait
crate:
#[async_trait]
trait Readable {
fn async readable(&self) -> io::Result<()>;
}
#[async_trait]
impl Readable for Reader {
async fn readable(&self) -> io::Result<()> {
do_stuff().await
}
}
async-trait
将async
方法转化为returnPin<Box<dyn Future<Output = ...> + Send = '_>>
的方法,和我们之前写的类似,所以也要考虑到和上面一样的地方。
为了避免将 Send
绑定放置在 async
trait 方法上,您可以在 trait 和 impl 块上调用异步 trait 宏作为 #[async_trait(?Send)]
。
不稳定的功能
如果你在夜间,故事会更好。您可以启用 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 { ... }
}
}
借用问题仍然适用于上述代码。但是,使用不稳定的特性 generic_associated_types
,您可以在整个生命周期内使 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<'_> {
let fut = async move {
println!("{}", self.0); // we can capture self!
Ok(())
};
Box::pin(fut)
}
}