- Notifications
You must be signed in to change notification settings - Fork0
Type erasure for async trait methods
License
Apache-2.0, MIT licenses found
Licenses found
workflow-rs/workflow-async-trait
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
This is a customized implementation of async trait that in addition toasync_trait exportsasync_trait_with_send andasync_trait_without_send attribute macros. This allows usingasync_trait(?Send) without the?Send qualifier, in-turn allowing use of#cfg[] conditional statements to alter betweenSend and?Send requirements based on features or platform architectures.
The initial round of stabilizations for the async/await language feature in Rust1.39 did not include support for async fn in traits. Trying to include an asyncfn in a trait produces the following error:
traitMyTrait{asyncfnf(){}}
error[E0706]: trait fns cannot be declared `async` --> src/main.rs:4:5 |4 | async fn f() {} | ^^^^^^^^^^^^^^^
This crate provides an attribute macro to make async fn in traits work.
Please refer towhy async fn in traits are hard for a deeper analysisof how this implementation differs from what the compiler and language hope todeliver in the future.
This example implements the core of a highly effective advertising platformusing async fn in a trait.
The only thing to notice here is that we write an#[async_trait] macro on topof traits and trait impls that contain async fn, and then they work.
use async_trait::async_trait;#[async_trait]traitAdvertisement{asyncfnrun(&self);}structModal;#[async_trait]implAdvertisementforModal{asyncfnrun(&self){self.render_fullscreen().await;for _in0..4u16{remind_user_to_join_mailing_list().await;}self.hide_for_now().await;}}structAutoplayingVideo{media_url:String,}#[async_trait]implAdvertisementforAutoplayingVideo{asyncfnrun(&self){let stream =connect(&self.media_url).await; stream.play().await;// Video probably persuaded user to join our mailing list!Modal.run().await;}}
It is the intention that all features of Rust traits should work nicely with#[async_trait], but the edge cases are numerous.Please file an issue if yousee unexpected borrow checker errors, type errors, or warnings. There is no useofunsafe in the expanded code, so rest assured that if your code compiles itcan't be that badly broken.
- 👍 Self by value, by reference, by mut reference, or no self;
- 👍 Any number of arguments, any return value;
- 👍 Generic type parameters and lifetime parameters;
- 👍 Associated types;
- 👍 Having async and non-async functions in the same trait;
- 👍 Default implementations provided by the trait;
- 👍 Elided lifetimes;
- 👍 Dyn-capable traits.
Async fns get transformed into methods that returnPin<Box<dyn Future + Send + 'async_trait>> and delegate to a private async freestanding function.
For example theimpl Advertisement for AutoplayingVideo above would beexpanded as:
implAdvertisementforAutoplayingVideo{fnrun<'async_trait>(&'async_traitself,) ->Pin<Box<dyn std::future::Future<Output =()> +Send +'async_trait>>whereSelf:Sync +'async_trait,{asyncfnrun(_self:&AutoplayingVideo){/* the original method body */}Box::pin(run(self))}}
Not all async traits need futures that aredyn Future + Send. To avoid havingSend and Sync bounds placed on the async trait methods, invoke the async traitmacro as#[async_trait(?Send)] on both the trait and the impl blocks.
Be aware that async fn syntax does not allow lifetime elision outside of& and&mut references. (This is true even when not using #[async_trait].)Lifetimes must be named or marked by the placeholder'_.
Fortunately the compiler is able to diagnose missing lifetimes with a good errormessage.
typeElided<'a> =&'ausize;#[async_trait]traitTest{asyncfntest(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: `<'_>`
The fix is to name the lifetime or use'_.
#[async_trait]traitTest{// eitherasyncfntest<'e>(elided:Elided<'e>){}// orasyncfntest(elided:Elided<'_>){}}
Traits with async methods can be used as trait objects as long as they meet theusual requirements for dyn -- no methods with type parameters, no self by value,no associated types, etc.
#[async_trait]pubtraitObjectSafe{asyncfnf(&self);asyncfng(&mutself);}implObjectSafeforMyType{...}let value:MyType = ...;let object =&valueas&dynObjectSafe;// make trait object
The one wrinkle is in traits that provide default implementations of asyncmethods. In order for the default implementation to produce a future that isSend, the async_trait macro must emit a bound ofSelf: Sync on trait methodsthat take&self and a boundSelf: Send on trait methods that take&mut self. An example of the former is visible in the expanded code in theexplanation section above.
If you make a trait with async methods that have default implementations,everything will work except that the trait cannot be used as a trait object.Creating a value of type&dyn Trait will produce an error that looks likethis:
error: the trait `Test` cannot be made into an object --> src/main.rs:8:5 |8 | async fn cannot_dyn(&self) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For traits that need to be object safe and need to have default implementationsfor some async methods, there are two resolutions. Either you can add Sendand/or Sync as supertraits (Send if there are&mut self methods with defaultimplementations, Sync if there are&self methods with default implementations)to constrain all implementors of the trait such that the default implementationsare applicable to them:
#[async_trait]pubtraitObjectSafe:Sync{// added supertraitasyncfncan_dyn(&self){}}let object =&valueas&dynObjectSafe;
or you can strike the problematic methods from your trait object by boundingthem withSelf: Sized:
#[async_trait]pubtraitObjectSafe{asyncfncannot_dyn(&self)whereSelf:Sized{}// presumably other methods}let object =&valueas&dynObjectSafe;
Licensed under either ofApache License, Version2.0 orMIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submittedfor inclusion in this crate by you, as defined in the Apache-2.0 license, shallbe dual licensed as above, without any additional terms or conditions.
About
Type erasure for async trait methods
Resources
License
Apache-2.0, MIT licenses found
Licenses found
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Languages
- Rust100.0%