#async-trait #traits #sync #future #patch #methods

async-trait-with-sync

async-trait 结合 Sync 补丁

2 个版本

0.1.36 2020年7月7日
0.1.31 2020年7月7日

#638过程宏

Download history 170/week @ 2024-03-11 447/week @ 2024-03-18 196/week @ 2024-03-25 424/week @ 2024-04-01 342/week @ 2024-04-08 105/week @ 2024-04-15 63/week @ 2024-04-22 54/week @ 2024-04-29 48/week @ 2024-05-06 45/week @ 2024-05-13 107/week @ 2024-05-20 35/week @ 2024-05-27 56/week @ 2024-06-03 54/week @ 2024-06-10 189/week @ 2024-06-17 206/week @ 2024-06-24

每月508 次下载
用于 11 个Crates(直接使用9个)

MIT/Apache

46KB
868

支持 Sync 的异步特性方法

该包完全基于 https://github.com/dtolnay/async-trait,除了增加一个小补丁,允许在这些future上派生 Sync(参见 https://github.com/dtolnay/async-trait/pull/96),这在目前是必须的,直到在 https://github.com/dtolnay/async-trait/issues/77 中解决的更广泛生态问题得到解决。


lib.rs:

githubcrates-iodocs-rs


异步特性方法的类型擦除

Rust 1.39中异步/await语言特性的初始稳定版本不包括对特性中异步fn的支持。尝试在特性中包含异步fn将产生以下错误

trait MyTrait {
    async fn f() {}
}
error[E0706]: trait fns cannot be declared `async`
 --> src/main.rs:4:5
  |
4 |     async fn f() {}
  |     ^^^^^^^^^^^^^^^

该包提供了一个属性宏,使特性中的异步fn能够正常工作。

请参阅为什么 trait 中的异步 fn 很难,以深入了解这种实现方式与编译器和语言在未来的预期有何不同。


示例

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

需要注意的是,我们在包含异步 fn 的 trait 和 trait impl 上编写了一个 #[async_trait] 宏,然后它们就可以工作了。

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 trait 的所有功能都应该很好地与 #[async_trait] 一起工作,但边缘情况却很多。如果您看到意外的借用检查器错误、类型错误或警告,请提交一个 issue。展开代码中未使用 unsafe,因此请放心,如果您的代码可以编译,那么它不太可能存在严重错误。

☑ 自身可以通过值、引用、可变引用或无 self 获取;
☑ 任何数量的参数,任何返回值;
☑ 泛型类型参数和生命周期参数;
☑ 关联类型;
☑ 在同一 trait 中有异步和非异步函数;
☑ 由 trait 提供的默认实现;
☑ 省略的生命周期;
☑ 支持 Dyn 的 trait。


说明

异步 fn 被转换为返回 Pin<Box<dyn Future + Send + 'async>> 的方法,并将委托给一个私有的异步独立函数。

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

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

        Box::pin(run(self))
    }
}



非线程安全 futures

并非所有异步 trait 都需要 dyn Future + Send 的 futures。为了避免在异步 trait 方法上放置 Send 和 Sync 约束,请在 trait 和 impl 块上调用异步 trait 宏为 #[async_trait(?Send)]


省略的生命周期

请注意,异步 fn 语法不允许在 &&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<'_>) {}
}



Dyn 特质

具有异步方法的特质可以作为 trait 对象使用,只要它们满足 dyn 的通常要求——没有类型参数的方法、没有通过值传递的 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 类型的 Future,async_trait 宏必须在接受 &self 的特性方法上发出一个 Sync 绑定,并在接受 &mut 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 方法,则 Sync)来约束特性的所有实现者,使得默认实现适用于它们

#
#[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;

依赖关系

~1.5MB
~35K SLoC