如何在特征中定义异步方法?

How can I define an async method in a trait?

我有一个特征,我用它来抽象掉 tokio::net::TcpStreamtokio::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-traitasync方法转化为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)
    }
}