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

rui*_*eco 6 rust rust-tokio

我有一个特征,我用它来抽象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)
  }
}
Run Code Online (Sandbox Code Playgroud)

问题是我想pub async fn readable(&self) -> io::Result<()>在这两种方法中都抽象出来,但是无法在特征中实现异步方法。我该如何处理?

Ibr*_*med 10

目前,async fn不能用于特质。造成这种情况的原因有些复杂,但未来有计划取消此限制。你可以参考一下为什么traits中的async fn很难对问题进行更深入的分析。

关联类型

同时,您可以使用关联类型:

trait Readable {
    type Output: Future<Output = io::Result<()>>;

    fn readable(&self) -> Self::Output;
}
Run Code Online (Sandbox Code Playgroud)

具体的未来类型

实现此特征时,您可以使用任何实现 的类型Future,例如Ready来自标准库:

use std::future;

impl Readable for Reader {
    type Output = future::Ready<io::Result<()>>;
    
    fn readable(&self) -> Self::Output {
        future::ready(Ok(()))
    }
}
Run Code Online (Sandbox Code Playgroud)

动态未来类型

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<Box<dyn Future<Output = io::Result<()>>>>;
    
    fn readable(&self) -> Self::Output {
        let fut = async {
            do_stuff().await
        };
        Box::pin(fut)
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,使用这些特征方法将导致每个函数调用的堆分配和动态分派。对于绝大多数应用程序来说,这不是一个很大的成本,但需要考虑。

捕获参考

可能出现的一个问题是关联类型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)
    }
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)

稳定 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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
    }
}
Run Code Online (Sandbox Code Playgroud)

async-traitasync方法转换为返回的方法Pin<Box<dyn Future<Output = ...> + Send = '_>>,类似于我们之前写的,所以也应该考虑与上面相同的点。

为了避免Sendasynctrait 方法上放置绑定,您可以像#[async_trait(?Send)]在 trait 和 impl 块上一样调用异步 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 { ... }
    }
}
Run Code Online (Sandbox Code Playgroud)

借用问题仍然适用于上述代码。但是,使用不稳定功能generic_associated_types,您可以Output在整个生命周期内进行泛型并捕获self

trait Readable {
    type Output<'a>: Future<Output = io::Result<()>>;

    fn readable(&self) -> Self::Output<'_>;
}
Run Code Online (Sandbox Code Playgroud)

前面的例子编译,零装箱!

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 还值得注意的是,“async-trait”可以通过在特征和实现上使用“#[async_trait(?Send)]”来避免“Send”绑定 (3认同)