#traits #async #erasure #fn #methods

no-std async-trait

异步 trait 方法的类型擦除

82 个版本

0.1.81 2024年7月7日
0.1.79 2024年3月24日
0.1.76 2023年12月30日
0.1.74 2023年10月15日
0.1.6 2019年7月27日

#77 in Rust 模式

Download history 1464766/week @ 2024-05-04 1550368/week @ 2024-05-11 1542571/week @ 2024-05-18 1492765/week @ 2024-05-25 1661420/week @ 2024-06-01 1592576/week @ 2024-06-08 1580170/week @ 2024-06-15 1616175/week @ 2024-06-22 1495927/week @ 2024-06-29 1695440/week @ 2024-07-06 1670725/week @ 2024-07-13 1762481/week @ 2024-07-20 1740010/week @ 2024-07-27 1739257/week @ 2024-08-03 1781818/week @ 2024-08-10 1490477/week @ 2024-08-17

7,046,082 每月下载量
用于 16,807 个包 (6,264 个直接使用)

MIT/Apache

52KB
810

异步 trait 方法

github crates.io docs.rs build status

Rust 1.75 中 traits 中异步函数的稳定化没有包括将包含异步函数的 traits 作为 dyn Trait 使用。尝试使用 dyn 与异步 trait 产生以下错误

pub trait Trait {
    async fn f(&self);
}

pub fn make() -> Box<dyn Trait> {
    unimplemented!()
}
error[E0038]: the trait `Trait` cannot be made into an object
 --> src/main.rs:5:22
  |
5 | pub fn make() -> Box<dyn Trait> {
  |                      ^^^^^^^^^ `Trait` cannot be made into an object
  |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <http://doc.rust-lang.net.cn/reference/items/traits.html#object-safety>
 --> src/main.rs:2:14
  |
1 | pub trait Trait {
  |           ----- this trait cannot be made into an object...
2 |     async fn f(&self);
  |              ^ ...because method `f` is `async`
  = help: consider moving `f` to another trait

此包提供了一个属性宏,使 traits 中的异步 fn 可以与 dyn traits 一起工作。

请参阅 为什么 traits 中的异步 fn 很难 了解此实现与编译器和语言原生提供的内容有何不同。


示例

此示例通过在 traits 中使用异步 fn 实现了一个高度有效的广告平台的核心。

需要注意的是,我们只在 traits 和包含异步 fn 的 trait 实现上编写了 #[async_trait] 宏,然后它们就可以工作了。例如,我们可以得到 Vec<Box<dyn Advertisement + Sync>>&[&dyn Advertisement]

use async_trait::async_trait;

#[async_trait]
trait Advertisement {
    async fn run(&self);
}

struct Modal;

#[async_trait]
impl Advertisement for Modal {
    async fn run(&self) {
        self.render_fullscreen().await;
        for _ in 0..4u16 {
            remind_user_to_join_mailing_list().await;
        }
        self.hide_for_now().await;
    }
}

struct AutoplayingVideo {
    media_url: String,
}

#[async_trait]
impl Advertisement for AutoplayingVideo {
    async fn run(&self) {
        let stream = connect(&self.media_url).await;
        stream.play().await;

        // Video probably persuaded user to join our mailing list!
        Modal.run().await;
    }
}

支持的功能

目标是让 Rust traits 的所有功能都很好地与 #[async_trait] 一起工作,但边缘情况众多。 如果您看到意外的借用检查器错误、类型错误或警告,请提交问题。 扩展代码中没有使用 unsafe,所以请放心,如果您的代码可以编译,那么它不会太糟糕。

  • 👍 按值、按引用、按可变引用或无 self
  • 👍 任意数量的参数,任意返回值
  • 👍 泛型类型参数和生命周期参数
  • 👍 关联类型
  • 👍 在同一个 trait 中有异步和非异步函数
  • 👍 trait 提供的默认实现
  • 👍 省略生命周期。

说明

异步函数被转换为返回类型为 Pin<Box<dyn Future + Send + 'async_trait>> 的方法,并将委托给异步块。

例如,上面的 impl Advertisement for AutoplayingVideo 将被展开为

impl Advertisement for AutoplayingVideo {
    fn run<'async_trait>(
        &'async_trait self,
    ) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'async_trait>>
    where
        Self: Sync + 'async_trait,
    {
        Box::pin(async move {
            /* the original method body */
        })
    }
}

非线程安全的未来

并非所有的异步特质都需要 dyn Future + Send 类型的未来。为了避免在异步特质方法上放置 Send 和 Sync 的界限,将异步特质宏作为 #[async_trait(?Send)] 在特质的 impl 块上调用。


省略生命周期

请注意,异步函数语法不允许在 &&mut 引用之外省略生命周期。(即使不使用 #[async_trait],这也适用。)生命周期必须被命名或用占位符 '_ 标记。

幸运的是,编译器能够通过良好的错误信息诊断缺失的生命周期。

type Elided<'a> = &'a usize;

#[async_trait]
trait Test {
    async fn test(not_okay: Elided, okay: &usize) {}
}
error[E0726]: implicit elided lifetime not allowed here
 --> src/main.rs:9:29
  |
9 |     async fn test(not_okay: Elided, okay: &usize) {}
  |                             ^^^^^^- help: indicate the anonymous lifetime: `<'_>`

修复方法是命名生命周期或使用 '_

#[async_trait]
trait Test {
    // either
    async fn test<'e>(elided: Elided<'e>) {}
    // or
    async fn test(elided: Elided<'_>) {}
}

动态特质

具有异步方法特质可以用作特质对象,只要它们满足动态的常规要求——没有类型参数的方法,没有值 self,没有关联类型等。

#[async_trait]
pub trait ObjectSafe {
    async fn f(&self);
    async fn g(&mut self);
}

impl ObjectSafe for MyType {...}

let value: MyType = ...;
let object = &value as &dyn ObjectSafe;  // make trait object

一个难题在于提供异步方法默认实现的特质。为了使默认实现产生一个 Send 的未来,异步特质宏必须在取 &self 的特质方法上发出 Self: Sync 的界限,并在取 &mut self 的特质方法上发出 Self: Send 的界限。上述解释部分展开代码中的一个例子是前者。

如果你创建一个具有默认实现的异步方法的特质,除了特质不能用作特质对象外,一切都会正常工作。创建类型为 &dyn Trait 的值将产生一个类似这样的错误

error: the trait `Test` cannot be made into an object
 --> src/main.rs:8:5
  |
8 |     async fn cannot_dyn(&self) {}
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

对于需要是对象安全和需要为一些异步方法提供默认实现的特质,有两种解决方案。你可以添加 Send 和/或 Sync 作为超特质(如果存在具有默认实现的 &mut self 方法,Send 如果存在具有默认实现的 &self 方法)来约束特质的所有实现者,以便默认实现适用于它们

#[async_trait]
pub trait ObjectSafe: Sync {  // added supertrait
    async fn can_dyn(&self) {}
}

let object = &value as &dyn ObjectSafe;

或者你可以通过将它们与 Self: Sized 界限来从你的特质对象中删除有问题的方法

#[async_trait]
pub trait ObjectSafe {
    async fn cannot_dyn(&self) where Self: Sized {}

    // presumably other methods
}

let object = &value as &dyn ObjectSafe;

许可证

根据您的选择,本软件许可协议适用于Apache License, Version 2.0MIT许可证
除非您明确声明,否则您根据Apache-2.0许可证定义的任何有意提交以包含在本软件包中的贡献,应按上述方式双许可,不得附加任何额外条款或条件。

依赖关系

约290–750KB
约18K SLoC