#async-trait #traits #methods #erasure #samotop #lifetime #future

samotop-async-trait

异步特质方法的类型擦除

3 个版本

0.2.1 2020年10月29日
0.2.0-samotop-dev2020年10月28日

1658过程宏

Download history 3/week @ 2024-03-11 32/week @ 2024-04-01

每月下载量 114

MIT/Apache

55KB
769

samotop-async-trait 0.2.1

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() {}
  |     ^^^^^^^^^^^^^^^

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

请参考 为什么特质中的异步 fn 很难实现 了解该实现与编译器和语言未来希望提供的内容有何不同。


示例

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

在这里需要注意的唯一事情是,我们在包含异步函数的特性和特型实现上写了一个#[async_trait]宏,然后它们就能正常工作了。

use samotop_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特型的所有功能都能很好地与##[async_trait]一起工作,但是边缘情况很多。如果您看到意外的借用检查错误、类型错误或警告,请提交一个问题。展开代码中没有任何unsafe的使用,因此请放心,如果您的代码可以编译,它就不可能太糟糕。

☑ 自身可以通过值、引用、可变引用或没有自身;
☑ 任意数量的参数,任意返回值;
☑ 泛型类型参数和生命周期参数;
☑ 关联类型;
☑ 在同一特型中拥有异步和非异步函数;
☑ 特型提供的默认实现;
☑ 省略的生命周期;
☑ 具有动态能力的特型。
☑ 通过#[future_is[BOUND]]选择更严格的futures界限。


说明

异步函数被转换成返回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 core::future::Future<Output = ()> + Send + 'async_trait>>
    where
        Self: Sync + 'async_trait,
    {
        let fut = async move {
            /* the original method body */
        };

        Box::pin(fut)
    }
}



非线程安全的futures

并非所有的异步特型都需要dyn Future + Send的futures。为了避免在异步特型方法上放置Send和Sync界限,请在特型和实现块上同时调用异步特型宏为#[async_trait(?Send)]


省略的生命周期

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

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

# use samotop_async_trait::async_trait;
#
type Elided<'a> = &'a usize;

#[async_trait]
trait Test {
    async fn test(elided: Elided, okay: &usize) -> &usize { elided }
}
error[E0106]: missing lifetime specifier
   |
19 |     async fn test(elided: Elided, okay: &usize) -> &usize { elided }
   |                           ------        ------     ^ expected named lifetime parameter
   |
   = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `elided` or `okay`
note: these named lifetimes are available to use
   |
17 | #[async_trait]
   | ^^^^^^^^^^^^^^

修复方法是命名生命周期。

#[async_trait]
trait Test {
    // either
    async fn test<'e>(elided: Elided<'e>, okay: &usize) -> &'e usize { 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类型的future,async_trait宏必须在接收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方法,则添加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;



使用#[future_is[BOUND]]更严格的future约束

你可以要求future是Sync或'static,甚至两者都要

#[async_trait]
trait SyncStaticFutures {
    #[future_is[Sync + 'static]]
    async fn sync_and_static(&self) -> String;
}
fn test<T:SyncStaticFutures>(tested:T)
{
    is_sync(tested.sync_and_static());
    is_static(tested.sync_and_static());
}
fn is_sync<T: Sync>(_tester: T) {}
fn is_static<T: 'static>(_tester: T) {}

实现静态future的问题在于,一旦异步块捕获了对self的引用,例如,异步块就不能是静态的。解决方案:在异步块外部使用async_setup_ready!宏来执行future的设置

#[async_trait]
trait SyncStaticFutures {
    #[future_is[Sync + 'static]]
    async fn sync_and_static(&self) -> String;
}

struct Dummy{
    message: String
}

#[async_trait]
impl SyncStaticFutures for Dummy {
    #[future_is[Sync + 'static]]
    async fn sync_and_static(&self) -> String
    {
        let msg = self.message.clone();
        let msg = async move {msg}; // aka future::ready(msg)
        async_setup_ready!();
        // your async/await business here...
        // for instance reading the string from the given file path
        msg.await
    }
}

致谢

这是dtolnay的async-trait的分支

许可证 - MIT OR Apache-2.0

根据您的选择,在以下任一许可证下授权:Apache License, Version 2.0或MIT许可证。
除非您明确声明,否则根据Apache-2.0许可证定义的,您有意提交以包含在此crate中的任何贡献,都将根据上述许可证双授权,没有额外的条款或条件。

依赖项

~1.5MB
~35K SLoC