作者:爱娟一辈子-_709 | 来源:互联网 | 2023-10-10 21:56
我有一个特征,我用它来抽象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;
}
impl TryRead for TcpStream {
fn do_try_read(&self, buf: &mut [u8]) -> Result {
self.try_read(buf)
}
}
问题是我想pub async fn readable(&self) -> io::Result<()>
在这两种方法中都抽象出来,但是无法在特征中实现异步方法。我该如何处理?
回答
目前,async fn
不能用于特质。造成这种情况的原因有些复杂,但未来有计划取消此限制。你可以参考一下为什么traits中的async fn很难对问题进行更深入的分析。
关联类型
同时,您可以使用关联类型:
trait Readable {
type Output: Future
具体的未来类型
实现此特征时,您可以使用任何实现 的类型Future
,例如Ready
来自标准库:
use std::future;
impl Readable for Reader {
type Output = future::Ready>; fn readable(&self) -> Self::Output {
future::ready(Ok(()))
}
}
动态未来类型
async
函数返回一个 opaque impl Future
,所以如果你需要调用一个,你没有一个具体的类型来设置Output
。相反,您可以返回一个动态类型的Future
:
impl Readable for Reader {
// or use the handy type alias from the futures crate:
// futures::BoxFuture<'static, io::Result<()>>
type Output = Pin>>>; 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>>>; 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> + '_>>;
}
impl Readable for Reader {
fn readable(&self) -> Pin> + '_>> {
let fut = async move {
println!("{}", self.0);
Ok(())
};
Box::pin(fut)
}
}
async_trait
为了避免这些样板文件,您可以使用async-trait
板条箱:
#[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
方法转换为返回的方法Pin + Send = '_>>
,类似于我们之前写的,所以也应该考虑与上面相同的点。
为了避免Send
在async
trait 方法上放置绑定,您可以像#[async_trait(?Send)]
在 trait 和 impl 块上一样调用异步 trait 宏。
不稳定的特性
如果你在夜间,故事会更好。您可以启用该type_alias_impl_trait
功能并使用常规async/await
语法而无需装箱:
#![feature(type_alias_impl_trait)]
trait Readable {
type Output: Future
借用问题仍然适用于上述代码。但是,使用不稳定功能generic_associated_types
,您可以Output
在整个生命周期内进行泛型并捕获self
:
trait Readable {
type Output<'a>: Future>;
fn readable(&self) -> Self::Output<'_>;
}
前面的例子编译,零装箱!
struct Reader(String);
impl Readable for Reader {
type Output<'a> = impl Future> + 'a; fn readable(&self) -> Self::Output<'_> {
let fut = async move {
println!("{}", self.0); // we can capture self!
Ok(())
};
Box::pin(fut)
}
}