Andrew Walbran | d1b91c7 | 2020-08-11 17:12:08 +0100 | [diff] [blame] | 1 | Async trait methods |
| 2 | =================== |
| 3 | |
| 4 | [<img alt="github" src="https://img.shields.io/badge/github-dtolnay/async--trait-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/async-trait) |
| 5 | [<img alt="crates.io" src="https://img.shields.io/crates/v/async-trait.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/async-trait) |
| 6 | [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-async--trait-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K" height="20">](https://docs.rs/async-trait) |
| 7 | [<img alt="build status" src="https://img.shields.io/github/workflow/status/dtolnay/async-trait/CI/master?style=for-the-badge" height="20">](https://github.com/dtolnay/async-trait/actions?query=branch%3Amaster) |
| 8 | |
| 9 | The initial round of stabilizations for the async/await language feature in Rust |
| 10 | 1.39 did not include support for async fn in traits. Trying to include an async |
| 11 | fn in a trait produces the following error: |
| 12 | |
| 13 | ```rust |
| 14 | trait MyTrait { |
| 15 | async fn f() {} |
| 16 | } |
| 17 | ``` |
| 18 | |
| 19 | ```console |
| 20 | error[E0706]: trait fns cannot be declared `async` |
| 21 | --> src/main.rs:4:5 |
| 22 | | |
| 23 | 4 | async fn f() {} |
| 24 | | ^^^^^^^^^^^^^^^ |
| 25 | ``` |
| 26 | |
| 27 | This crate provides an attribute macro to make async fn in traits work. |
| 28 | |
| 29 | Please refer to [*why async fn in traits are hard*][hard] for a deeper analysis |
| 30 | of how this implementation differs from what the compiler and language hope to |
| 31 | deliver in the future. |
| 32 | |
| 33 | [hard]: https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/ |
| 34 | |
| 35 | <br> |
| 36 | |
| 37 | ## Example |
| 38 | |
| 39 | This example implements the core of a highly effective advertising platform |
| 40 | using async fn in a trait. |
| 41 | |
| 42 | The only thing to notice here is that we write an `#[async_trait]` macro on top |
| 43 | of traits and trait impls that contain async fn, and then they work. |
| 44 | |
| 45 | ```rust |
| 46 | use async_trait::async_trait; |
| 47 | |
| 48 | #[async_trait] |
| 49 | trait Advertisement { |
| 50 | async fn run(&self); |
| 51 | } |
| 52 | |
| 53 | struct Modal; |
| 54 | |
| 55 | #[async_trait] |
| 56 | impl Advertisement for Modal { |
| 57 | async fn run(&self) { |
| 58 | self.render_fullscreen().await; |
| 59 | for _ in 0..4u16 { |
| 60 | remind_user_to_join_mailing_list().await; |
| 61 | } |
| 62 | self.hide_for_now().await; |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | struct AutoplayingVideo { |
| 67 | media_url: String, |
| 68 | } |
| 69 | |
| 70 | #[async_trait] |
| 71 | impl Advertisement for AutoplayingVideo { |
| 72 | async fn run(&self) { |
| 73 | let stream = connect(&self.media_url).await; |
| 74 | stream.play().await; |
| 75 | |
| 76 | // Video probably persuaded user to join our mailing list! |
| 77 | Modal.run().await; |
| 78 | } |
| 79 | } |
| 80 | ``` |
| 81 | |
| 82 | <br> |
| 83 | |
| 84 | ## Supported features |
| 85 | |
| 86 | It is the intention that all features of Rust traits should work nicely with |
| 87 | \#\[async_trait\], but the edge cases are numerous. *Please file an issue if you |
| 88 | see unexpected borrow checker errors, type errors, or warnings.* There is no use |
| 89 | of `unsafe` in the expanded code, so rest assured that if your code compiles it |
| 90 | can't be that badly broken. |
| 91 | |
| 92 | - 👍 Self by value, by reference, by mut reference, or no self; |
| 93 | - 👍 Any number of arguments, any return value; |
| 94 | - 👍 Generic type parameters and lifetime parameters; |
| 95 | - 👍 Associated types; |
| 96 | - 👍 Having async and non-async functions in the same trait; |
| 97 | - 👍 Default implementations provided by the trait; |
| 98 | - 👍 Elided lifetimes; |
| 99 | - 👍 Dyn-capable traits. |
| 100 | |
| 101 | <br> |
| 102 | |
| 103 | ## Explanation |
| 104 | |
| 105 | Async fns get transformed into methods that return `Pin<Box<dyn Future + Send + |
Haibo Huang | d8abf3d | 2020-08-17 15:39:53 -0700 | [diff] [blame] | 106 | 'async_trait>>` and delegate to a private async freestanding function. |
Andrew Walbran | d1b91c7 | 2020-08-11 17:12:08 +0100 | [diff] [blame] | 107 | |
| 108 | For example the `impl Advertisement for AutoplayingVideo` above would be |
| 109 | expanded as: |
| 110 | |
| 111 | ```rust |
| 112 | impl Advertisement for AutoplayingVideo { |
Haibo Huang | d8abf3d | 2020-08-17 15:39:53 -0700 | [diff] [blame] | 113 | fn run<'async_trait>( |
| 114 | &'async_trait self, |
| 115 | ) -> Pin<Box<dyn std::future::Future<Output = ()> + Send + 'async_trait>> |
Andrew Walbran | d1b91c7 | 2020-08-11 17:12:08 +0100 | [diff] [blame] | 116 | where |
Haibo Huang | d8abf3d | 2020-08-17 15:39:53 -0700 | [diff] [blame] | 117 | Self: Sync + 'async_trait, |
Andrew Walbran | d1b91c7 | 2020-08-11 17:12:08 +0100 | [diff] [blame] | 118 | { |
| 119 | async fn run(_self: &AutoplayingVideo) { |
| 120 | /* the original method body */ |
| 121 | } |
| 122 | |
| 123 | Box::pin(run(self)) |
| 124 | } |
| 125 | } |
| 126 | ``` |
| 127 | |
| 128 | <br> |
| 129 | |
| 130 | ## Non-threadsafe futures |
| 131 | |
| 132 | Not all async traits need futures that are `dyn Future + Send`. To avoid having |
| 133 | Send and Sync bounds placed on the async trait methods, invoke the async trait |
| 134 | macro as `#[async_trait(?Send)]` on both the trait and the impl blocks. |
| 135 | |
| 136 | <br> |
| 137 | |
| 138 | ## Elided lifetimes |
| 139 | |
| 140 | Be aware that async fn syntax does not allow lifetime elision outside of `&` and |
| 141 | `&mut` references. (This is true even when not using #\[async_trait\].) |
| 142 | Lifetimes must be named or marked by the placeholder `'_`. |
| 143 | |
| 144 | Fortunately the compiler is able to diagnose missing lifetimes with a good error |
| 145 | message. |
| 146 | |
| 147 | ```rust |
| 148 | type Elided<'a> = &'a usize; |
| 149 | |
| 150 | #[async_trait] |
| 151 | trait Test { |
| 152 | async fn test(not_okay: Elided, okay: &usize) {} |
| 153 | } |
| 154 | ``` |
| 155 | |
| 156 | ```console |
| 157 | error[E0726]: implicit elided lifetime not allowed here |
| 158 | --> src/main.rs:9:29 |
| 159 | | |
| 160 | 9 | async fn test(not_okay: Elided, okay: &usize) {} |
| 161 | | ^^^^^^- help: indicate the anonymous lifetime: `<'_>` |
| 162 | ``` |
| 163 | |
| 164 | The fix is to name the lifetime or use `'_`. |
| 165 | |
| 166 | ```rust |
| 167 | #[async_trait] |
| 168 | trait Test { |
| 169 | // either |
| 170 | async fn test<'e>(elided: Elided<'e>) {} |
| 171 | // or |
| 172 | async fn test(elided: Elided<'_>) {} |
| 173 | } |
| 174 | ``` |
| 175 | |
| 176 | <br> |
| 177 | |
| 178 | ## Dyn traits |
| 179 | |
| 180 | Traits with async methods can be used as trait objects as long as they meet the |
| 181 | usual requirements for dyn -- no methods with type parameters, no self by value, |
| 182 | no associated types, etc. |
| 183 | |
| 184 | ```rust |
| 185 | #[async_trait] |
| 186 | pub trait ObjectSafe { |
| 187 | async fn f(&self); |
| 188 | async fn g(&mut self); |
| 189 | } |
| 190 | |
| 191 | impl ObjectSafe for MyType {...} |
| 192 | |
| 193 | let value: MyType = ...; |
| 194 | let object = &value as &dyn ObjectSafe; // make trait object |
| 195 | ``` |
| 196 | |
| 197 | The one wrinkle is in traits that provide default implementations of async |
| 198 | methods. In order for the default implementation to produce a future that is |
| 199 | Send, the async\_trait macro must emit a bound of `Self: Sync` on trait methods |
| 200 | that take `&self` and a bound `Self: Send` on trait methods that take `&mut |
| 201 | self`. An example of the former is visible in the expanded code in the |
| 202 | explanation section above. |
| 203 | |
| 204 | If you make a trait with async methods that have default implementations, |
| 205 | everything will work except that the trait cannot be used as a trait object. |
| 206 | Creating a value of type `&dyn Trait` will produce an error that looks like |
| 207 | this: |
| 208 | |
| 209 | ```console |
| 210 | error: the trait `Test` cannot be made into an object |
| 211 | --> src/main.rs:8:5 |
| 212 | | |
| 213 | 8 | async fn cannot_dyn(&self) {} |
| 214 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 215 | ``` |
| 216 | |
| 217 | For traits that need to be object safe and need to have default implementations |
| 218 | for some async methods, there are two resolutions. Either you can add Send |
| 219 | and/or Sync as supertraits (Send if there are `&mut self` methods with default |
Joel Galenson | aa9cbeb | 2021-08-09 10:24:16 -0700 | [diff] [blame] | 220 | implementations, Sync if there are `&self` methods with default implementations) |
Andrew Walbran | d1b91c7 | 2020-08-11 17:12:08 +0100 | [diff] [blame] | 221 | to constrain all implementors of the trait such that the default implementations |
| 222 | are applicable to them: |
| 223 | |
| 224 | ```rust |
| 225 | #[async_trait] |
| 226 | pub trait ObjectSafe: Sync { // added supertrait |
| 227 | async fn can_dyn(&self) {} |
| 228 | } |
| 229 | |
| 230 | let object = &value as &dyn ObjectSafe; |
| 231 | ``` |
| 232 | |
| 233 | or you can strike the problematic methods from your trait object by bounding |
| 234 | them with `Self: Sized`: |
| 235 | |
| 236 | ```rust |
| 237 | #[async_trait] |
| 238 | pub trait ObjectSafe { |
| 239 | async fn cannot_dyn(&self) where Self: Sized {} |
| 240 | |
| 241 | // presumably other methods |
| 242 | } |
| 243 | |
| 244 | let object = &value as &dyn ObjectSafe; |
| 245 | ``` |
| 246 | |
| 247 | <br> |
| 248 | |
| 249 | #### License |
| 250 | |
| 251 | <sup> |
| 252 | Licensed under either of <a href="LICENSE-APACHE">Apache License, Version |
| 253 | 2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option. |
| 254 | </sup> |
| 255 | |
| 256 | <br> |
| 257 | |
| 258 | <sub> |
| 259 | Unless you explicitly state otherwise, any contribution intentionally submitted |
| 260 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall |
| 261 | be dual licensed as above, without any additional terms or conditions. |
| 262 | </sub> |