Skip to content

Commit 910f865

Browse files
tmandrytraviscrossyoshuawuytsnikomatsakis
authored
Announce async fn and return-position impl Trait in traits (#1180)
* Announce `async fn` and return-position `impl Trait` in traits * Apply suggestions from code review Co-authored-by: Travis Cross <[email protected]> Co-authored-by: Yosh <[email protected]> Co-authored-by: Niko Matsakis <[email protected]> * Apply feedback from code review And add a note about why we don't warn on non-public traits. * Clarify use of RPIT(IT) and its limitations * Add paragraph about future roadmap goals * Clarify that new features can build on top of `async fn` in traits * Apply suggestions from code review Co-authored-by: Travis Cross <[email protected]> * Remove footnote on desugaring After reading through it just felt too disruptive in the first section, and there isn't really a good place to put it. The FAQ still addresses the question. * Only one link to the crate/docs is necessary * Move blog post reference to FAQ And clarify footnote --------- Co-authored-by: Travis Cross <[email protected]> Co-authored-by: Yosh <[email protected]> Co-authored-by: Niko Matsakis <[email protected]>
1 parent 14fb3c2 commit 910f865

File tree

1 file changed

+237
-0
lines changed

1 file changed

+237
-0
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
---
2+
layout: post
3+
title: "Announcing `async fn` and return-position `impl Trait` in traits"
4+
author: Tyler Mandry
5+
team: The Async Working Group <https://www.rust-lang.org/governance/wgs/wg-async>
6+
---
7+
8+
The Rust Async Working Group is excited to announce major progress towards our goal of enabling the use of `async fn` in traits. Rust 1.75, which hits stable next week, will include support for both `-> impl Trait` notation and `async fn` in traits.
9+
10+
This is a big milestone, and we know many users will be itching to try these out in their own code. However, we are still missing some important features that many users need. Read on for recommendations on when and how to use the stabilized features.
11+
12+
## What's stabilizing
13+
14+
Ever since the stabilization of [RFC #1522] in Rust 1.26, Rust has allowed users to write `impl Trait` as the return type of functions (often called "RPIT"). This means that the function returns "some type that implements `Trait`". This is commonly used to return closures, iterators, and other types that are complex or impossible to write explicitly.
15+
16+
[RFC #1522]: https://rust-lang.github.io/rfcs/1522-conservative-impl-trait.html
17+
18+
```rust
19+
/// Given a list of players, return an iterator
20+
/// over their names.
21+
fn player_names(
22+
players: &[Player]
23+
) -> impl Iterator<Item = &String> {
24+
players
25+
.iter()
26+
.map(|p| &p.name)
27+
}
28+
```
29+
30+
Starting in Rust 1.75, you can use **return-position `impl Trait` in trait** (RPITIT) definitions and in trait impls. For example, you could use this to write a trait method that returns an iterator:
31+
32+
```rust
33+
trait Container {
34+
fn items(&self) -> impl Iterator<Item = Widget>;
35+
}
36+
37+
impl Container for MyContainer {
38+
fn items(&self) -> impl Iterator<Item = Widget> {
39+
self.items.iter().cloned()
40+
}
41+
}
42+
```
43+
44+
So what does all of this have to do with async functions? Well, async functions are "just sugar" for functions that return `-> impl Future`. Since these are now permitted in traits, **we also permit you to write traits that use `async fn`**.
45+
46+
```rust
47+
trait HttpService {
48+
async fn fetch(&self, url: Url) -> HtmlBody;
49+
// ^^^^^^^^ desugars to:
50+
// fn fetch(&self, url: Url) -> impl Future<Output = HtmlBody>;
51+
}
52+
```
53+
54+
## Where the gaps lie
55+
56+
### `-> impl Trait` in public traits
57+
58+
The use of `-> impl Trait` is still discouraged for general use in **public** traits and APIs for the reason that users can't put additional bounds on the return type. For example, there is no way to write this function in a way that is generic over the `Container` trait:
59+
60+
```rust
61+
fn print_in_reverse(container: impl Container) {
62+
for item in container.items().rev() {
63+
// ERROR: ^^^
64+
// the trait `DoubleEndedIterator`
65+
// is not implemented for
66+
// `impl Iterator<Item = Widget>`
67+
eprintln!("{item}");
68+
}
69+
}
70+
```
71+
72+
Even though some implementations might return an iterator that implements `DoubleEndedIterator`, there is no way for generic code to take advantage of this without defining another trait. In the future we plan to add a solution for this. For now, `-> impl Trait` is best used in internal traits or when you're confident your users won't need additional bounds. Otherwise you should consider using an associated type.[^nameable]
73+
74+
[^nameable]: Note that associated types can only be used in cases where the type is nameable. This restriction will be lifted once [`impl_trait_in_assoc_type`](https://github.com/rust-lang/rust/issues/63063) is stabilized.
75+
76+
### `async fn` in public traits
77+
78+
Since `async fn` desugars to `-> impl Future`, the same limitations apply. In fact, if you use bare `async fn` in a public trait today, you'll see a warning.
79+
80+
```
81+
warning: use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
82+
--> src/lib.rs:7:5
83+
|
84+
7 | async fn fetch(&self, url: Url) -> HtmlBody;
85+
| ^^^^^
86+
|
87+
help: you can desugar to a normal `fn` that returns `impl Future` and add any desired bounds such as `Send`, but these cannot be relaxed without a breaking API change
88+
|
89+
7 - async fn fetch(&self, url: Url) -> HtmlBody;
90+
7 + fn fetch(&self, url: Url) -> impl std::future::Future<Output = HtmlBody> + Send;
91+
|
92+
```
93+
94+
Of particular interest to users of async are `Send` bounds on the returned future. Since users cannot add bounds later, the error message is saying that you as a trait author need to make a choice: Do you want your trait to work with multithreaded, work-stealing executors?
95+
96+
Thankfully, we have a solution that allows using `async fn` in public traits today! We recommend using the `trait_variant::make` proc macro to let your users choose. This proc macro is part of the [`trait-variant`](https://crates.io/crates/trait-variant) crate, published by the rust-lang org. Add it to your project with `cargo add trait-variant`, then use it like so:
97+
98+
```rust
99+
#[trait_variant::make(HttpService: Send)]
100+
pub trait LocalHttpService {
101+
async fn fetch(&self, url: Url) -> HtmlBody;
102+
}
103+
```
104+
105+
This creates *two* versions of your trait: `LocalHttpService` for single-threaded executors and `HttpService` for multithreaded work-stealing executors. Since we expect the latter to be used more commonly, it has the shorter name in this example. It has additional Send bounds:
106+
107+
```rust
108+
pub trait HttpService: Send {
109+
fn fetch(
110+
&self,
111+
url: Url,
112+
) -> impl Future<Output = HtmlBody> + Send;
113+
}
114+
```
115+
116+
This macro works for async because `impl Future` rarely requires additional bounds other than Send, so we can set our users up for success. See the FAQ below for an example of where this is needed.
117+
118+
### Dynamic dispatch
119+
120+
Traits that use `-> impl Trait` and `async fn` are not object-safe, which means they lack support for dynamic dispatch. We plan to provide utilities that enable dynamic dispatch in an upcoming version of the `trait-variant` crate.
121+
122+
## How we hope to improve in the future
123+
124+
In the future we would like to allow users to add their own bounds to `impl Trait` return types, which would make them more generally useful. It would also enable more advanced uses of `async fn`. The syntax might look something like this:
125+
126+
```rust
127+
trait HttpService = LocalHttpService<fetch(): Send> + Send;
128+
```
129+
130+
Since these aliases won't require any support on the part of the trait author, it will technically make the Send variants of async traits unnecessary. However, those variants will still be a nice convenience for users, so we expect that most crates will continue to provide them.
131+
132+
Of course, the goals of the Async Working Group don't stop with `async fn` in traits. We want to continue building features on top of it that enable more reliable and sophisticated use of async Rust, and we intend to publish a more extensive roadmap in the new year.
133+
134+
## Frequently asked questions
135+
136+
### Is it okay to use `-> impl Trait` in traits?
137+
138+
For private traits you can use `-> impl Trait` freely. For public traits, it's best to avoid them for now unless you can anticipate all the bounds your users might want (in which case you can use `#[trait_variant::make]`, as we do for async). We expect to lift this restriction in the future.
139+
140+
### Should I still use the `#[async_trait]` macro?
141+
142+
There are a couple of reasons you might need to continue using async-trait:
143+
144+
* You want to support Rust versions older than 1.75.
145+
* You want dynamic dispatch.
146+
147+
As stated above, we hope to enable dynamic dispatch in a future version of the `trait-variant` crate.
148+
149+
### Is it okay to use `async fn` in traits? What are the limitations?
150+
151+
Assuming you don't need to use `#[async_trait]` for one of the reasons stated above, it's totally fine to use regular `async fn` in traits. Just remember to use `#[trait_variant::make]` if you want to support multithreaded runtimes.
152+
153+
The biggest limitation is that a type must always decide if it implements the Send or non-Send version of a trait. It cannot implement the Send version *conditionally* on one of its generics. This can come up in the [middleware](https://github.com/tower-rs/tower/blob/master/guides/building-a-middleware-from-scratch.md) pattern, for example, `RequestLimitingService<T>` that is HttpService if `T: HttpService`.
154+
155+
### Why do I need `#[trait_variant::make]` and `Send` bounds?
156+
157+
In simple cases you may find that your trait appears to work fine with a multithreaded executor. There are some patterns that just won't work, however. Consider the following:
158+
159+
```rust
160+
fn spawn_task(service: impl HttpService + 'static) {
161+
tokio::spawn(async move {
162+
let url = Url::from("https://rust-lang.org");
163+
let _body = service.fetch(url).await;
164+
});
165+
}
166+
```
167+
168+
Without Send bounds on our trait, this would fail to compile with the error: "future cannot be sent between threads safely". By creating a variant of your trait with Send bounds, you avoid sending your users into this trap.
169+
170+
Note that you won't see a warning if your trait is not public, because if you run into this problem you can always add the Send bounds yourself later.
171+
172+
For a more thorough explanation of the problem, see [this blog post](https://blog.rust-lang.org/inside-rust/2023/05/03/stabilizing-async-fn-in-trait.html).[^cut-scope]
173+
174+
[^cut-scope]: Note that in that blog post we originally said we would solve the Send bound problem before shipping `async fn` in traits, but we decided to cut that from the scope and ship the `trait-variant` crate instead.
175+
176+
### Can I mix async fn and impl trait?
177+
178+
Yes, you can freely move between the `async fn` and `-> impl Future` spelling in your traits and impls. This is true even when one form has a Send bound.[^leakage] This makes the traits created by `trait_variant` nicer to use.
179+
180+
```rust
181+
trait HttpService: Send {
182+
fn fetch(&self, url: Url)
183+
-> impl Future<Output = HtmlBody> + Send;
184+
}
185+
186+
impl HttpService for MyService {
187+
async fn fetch(&self, url: Url) -> HtmlBody {
188+
// This works, as long as `do_fetch(): Send`!
189+
self.client.do_fetch(url).await.into_body()
190+
}
191+
}
192+
```
193+
194+
[^leakage]: This works because of *auto-trait leakage*, which allows knowledge of auto traits to "leak" from an item whose signature does not specify them.
195+
196+
### Why don't these signatures use `impl Future + '_`?
197+
198+
For `-> impl Trait` in traits we adopted the [2024 Capture Rules] early. This means that the `+ '_` you often see today is unnecessary in traits, because the return type is already assumed to capture input lifetimes. In the 2024 edition this rule will apply to all function signatures. See the linked RFC for more.
199+
200+
[2024 Capture Rules]: https://rust-lang.github.io/rfcs/3498-lifetime-capture-rules-2024.html
201+
202+
### Why am I getting a "refine" warning when I implement a trait with `-> impl Trait`?
203+
204+
If your impl signature includes more detailed information than the trait itself, you'll [get a warning](https://play.rust-lang.org/?version=beta&mode=debug&edition=2021&gist=6248cfe0419a693d1331ef47c729d1fe):
205+
206+
```rust
207+
pub trait Foo {
208+
fn foo(self) -> impl Debug;
209+
}
210+
211+
impl Foo for u32 {
212+
fn foo(self) -> String {
213+
// ^^^^^^
214+
// warning: impl trait in impl method signature does not match trait method signature
215+
self.to_string()
216+
}
217+
}
218+
```
219+
220+
The reason is that you may be leaking more details of your implementation than you meant to. For instance, should the following code compile?
221+
222+
```rust
223+
fn main() {
224+
// Did the implementer mean to allow
225+
// use of `Display`, or only `Debug` as
226+
// the trait says?
227+
println!("{}", 32.foo());
228+
}
229+
```
230+
231+
Thanks to [refined trait implementations][3245] it does compile, but the compiler asks you to confirm your intent to refine the trait interface with `#[allow(refining_impl_trait)]` on the impl.
232+
233+
[3245]: https://rust-lang.github.io/rfcs/3245-refined-impls.html
234+
235+
## Conclusion
236+
237+
The Async Working Group is excited to end 2023 by announcing the completion of our primary goal for the year! Thank you to everyone who helpfully participated in design, implementation, and stabilization discussions. Thanks also to the users of async Rust who have given great feedback over the years. We're looking forward to seeing what you build, and to delivering continued improvements in the years to come.

0 commit comments

Comments
 (0)