#async-trait #traits #methods #erasure #error #lifetime

已撤销 aqueue_trait

异步特质的类型擦除

0.1.0 2020年11月19日

#33#erasure

MIT/Apache

54KB
871

异步特质方法

github crates.io docs.rs build status

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能够正常工作。

请参考为什么特质中的异步fn很困难,以深入了解此实现与编译器和语言希望在未来交付的内容有何不同。


示例

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

这里需要注意的唯一一点是我们对包含异步fn的特质和特质实现使用#[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特质的全部功能都能很好地与#[async_trait]一起工作,但是边缘情况很多。如果您看到意外的借用检查器错误、类型错误或警告,请提交一个问题。展开代码中没有使用unsafe,所以请放心,如果您的代码能够编译,它不会那么糟糕。

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

说明

异步fn被转换为返回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,
    {
        async fn run(_self: &AutoplayingVideo) {
            /* the original method body */
        }

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

非线程安全 futures

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


省略的生命周期

请注意,异步函数语法不允许在 &&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 的 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 作为超特质添加,以便约束特质的所有实现者,使默认实现适用于它们

#[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 许可证的定义,您有意提交的任何贡献,都将如上所述双重许可,没有任何附加条款或条件。

依赖项

~1.5MB
~36K SLoC