You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: text/3654-return-type-notation.md
+30-31Lines changed: 30 additions & 31 deletions
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@
10
10
11
11
Return type notation (RTN) gives a way to reference or bound the type returned by a trait method. The new bounds look like `T: Trait<method(..): Send>` or `T::method(..): Send`. The primary use case is to add bounds such as `Send` to the futures returned by `async fn`s in traits and `-> impl Future` functions, but they work for any trait function defined with return-position impl trait (e.g., `where T: Factory<widgets(..): DoubleEndedIterator>` would also be valid).
12
12
13
-
This RFC proposes a new kind of type written `<T as Trait>::method(..)` (or `T::method(..)` for short). RTN refers to "the type returned by invoking `method` on `T`".
13
+
This RFC proposes a new kind of type written `<T as Trait>::method(..)` (or `T::method(..)` for short). RTN refers to "the type returned by invoking `method` on `T`".
14
14
15
15
To keep this RFC focused, it only covers usage of RTN as the `Self` type of a bound or where-clause. The expectation is that, after accepting this RFC, we will gradually expand RTN usage to other places as covered under [Future Possibilities](#future-possibilities). As a notable example, supporting RTN in struct field types would allow constructing types that store the results of a call to a trait `-> impl Trait` method, making them [more suitable for use in public APIs](https://rust-lang.github.io/api-guidelines/future-proofing.html).
16
16
@@ -43,7 +43,7 @@ To create an interoperable async ecosystem, we need the ability to write a singl
43
43
```rust
44
44
traitService<Request> {
45
45
typeResponse;
46
-
46
+
47
47
// Invoke the service.
48
48
asyncfncall(&self, req:Request) ->Self::Response;
49
49
}
@@ -60,7 +60,7 @@ where
60
60
R:Debug,
61
61
{
62
62
typeResponse=S::Response;
63
-
63
+
64
64
asyncfncall(&self, request:R) ->S::Response {
65
65
eprintln!("{request:?}");
66
66
self.0.call(request).await
@@ -73,7 +73,7 @@ Defining `Service` as shown above works fine in a thread-per-core or single-thre
73
73
74
74
```rust
75
75
asyncfnspawn_call<S>(service:S) ->S::Response
76
-
where
76
+
where
77
77
S:Service<(), Response:Send> +Send+ 'static,
78
78
{
79
79
tokio::spawn(asyncmove {
@@ -108,7 +108,7 @@ The only way today to make this code compile is to modify the `Service` trait de
108
108
```rust
109
109
traitSendService<Request>:Send {
110
110
typeResponse;
111
-
111
+
112
112
// Invoke the service.
113
113
fncall(
114
114
&self,
@@ -127,7 +127,7 @@ It is useful to compare this situation with analogous scenarios that arise elsew
127
127
fninto_iter_example<I:IntoIterator>(i:I) {
128
128
letiter=i.into_iter();
129
129
std::thread::spawn(move|| {
130
-
iter.next(); // <-- Error!
130
+
iter.next(); // <-- Error!
131
131
});
132
132
}
133
133
```
@@ -147,15 +147,15 @@ error[E0277]: `<I as IntoIterator>::IntoIter` cannot be sent between threads saf
147
147
| _____|__________________within this `{closure@src/lib.rs:3:24: 3:31}`
148
148
| | |
149
149
| | required by a bound introduced by this call
150
-
4 | | iter.next();
150
+
4 | | iter.next();
151
151
5 | | });
152
152
| |_____^ `<I as IntoIterator>::IntoIter` cannot be sent between threads safely
153
153
...
154
154
help: consider further restricting the associated type
155
155
|
156
156
1 | fn into_iter_example<I: IntoIterator>(i: I)
157
157
| where <I as IntoIterator>::IntoIter: Send {
158
-
|
158
+
|
159
159
```
160
160
161
161
There are two ways the function `into_iter_example` could be made to compile:
@@ -173,11 +173,11 @@ The core feature proposed in this RFC is the ability to write a bound that bound
173
173
174
174
```rust
175
175
asyncfnspawn_call<S>(service:S) ->S::Response
176
-
where
176
+
where
177
177
S:Service<
178
178
(),
179
179
Response:Send,
180
-
call(..):Send, // 👈 "the method `call`
180
+
call(..):Send, // 👈 "the method `call`
181
181
// returns a `Send` future"
182
182
> +Send+ 'static,
183
183
{
@@ -239,7 +239,7 @@ We expect most users in the wild to define "trait aliases" to indicate cases whe
239
239
// by async or `-> impl Trait` methods
240
240
traitService<Request> {
241
241
typeResponse;
242
-
242
+
243
243
// Invoke the service.
244
244
asyncfncall(&self, req:Request) ->Self::Response;
245
245
}
@@ -250,7 +250,7 @@ The expansion of this macro use RTN to create a trait that both (1) implies a `S
250
250
```rust
251
251
traitSendService<R>:// a `SendService` is...
252
252
Service< // ...a `Service`...
253
-
R,
253
+
R,
254
254
call(..):Send, // ...where `call` returns
255
255
// a `Send` future...
256
256
> +
@@ -267,7 +267,7 @@ The function `spawn_call` can then be written as follows:
267
267
268
268
```rust
269
269
asyncfnspawn_call<S>(service:S) ->S::Response
270
-
where
270
+
where
271
271
S:SendService<(), Response:Send> + 'static,
272
272
// 👆 use the alias
273
273
{
@@ -320,7 +320,7 @@ For traits whose futures may or may not be `Send`, the recommend pattern is to l
320
320
#[trait_variant::make(SendHealthCheck:Send)]
321
321
traitHealthCheck {
322
322
asyncfncheck(&mutself, server:&Server) ->bool;
323
-
323
+
324
324
asyncfnshutdown(&mutself, server:&Server);
325
325
}
326
326
```
@@ -337,7 +337,7 @@ impl HealthCheck for DummyCheck {
337
337
asyncfncheck(&mutself, server:&Server) ->bool {
338
338
true
339
339
}
340
-
340
+
341
341
asyncfnshutdown(&mutself, server:&Server) {}
342
342
}
343
343
```
@@ -375,9 +375,9 @@ where
375
375
// be Send because this code runs.
376
376
tokio::time::sleep(Duration::from_secs(1)).await;
377
377
}
378
-
378
+
379
379
emit_failure_log(&server).await;
380
-
380
+
381
381
server.shutdown().await;
382
382
// ----- Error: Returned future must be Send
383
383
// because this code runs.
@@ -416,7 +416,7 @@ For example, the following function spawns a task to shutdown the server:
The `shutdown(..)` notation acts like an associated type referring to the return type of the method.
445
445
The bound `HC: HealthCheck<shutdown(..): Send>` indicates that the `shutdown` method,
446
446
regardless of what arguments it is given,
447
-
will return a `Send` future.
447
+
will return a `Send` future.
448
448
These bounds do not have to be written in the `HealthCheck` trait, it could also be written as follows:
449
449
450
450
```rust
@@ -574,7 +574,7 @@ Examples: given the `Widgets` trait defined earlier in this section...
574
574
*`T: Widgets<widgets(..): Send>` is a valid associated type bound
575
575
576
576
RTN bounds are internally desugared to an RTN in a standalone where-clause,
577
-
so e.g. `where T: Widgets<widgets(..): Send>` becomes `where <T as Widgets>::widgets(..): Send`.
577
+
so e.g. `where T: Widgets<widgets(..): Send>` becomes `where <T as Widgets>::widgets(..): Send`.
578
578
We will not consider them further in this section.
579
579
580
580
## Where RTN can be used (for now)
@@ -690,7 +690,7 @@ The Async Working Group has performed [five case studies][cs] around the use of
690
690
We found that all of these key use cases required a way to handle send bounds, with only two exceptions:
691
691
692
692
*`embassy`, where the entire process is single-threaded (and hence `Send` is not important),
693
-
* Fuchsia, where the developers at first thought they needed `Send` bounds, but ultimately found they were able to refactor so that spawns did not occur in generic code ([link to the relevant section](https://rust-lang.github.io/async-fundamentals-initiative/evaluation/case-studies/socket-handler.html#send-bound-limitation)).
693
+
* Fuchsia, where the developers at first thought they needed `Send` bounds, but ultimately found they were able to refactor so that spawns did not occur in generic code ([link to the relevant section](https://rust-lang.github.io/async-fundamentals-initiative/evaluation/case-studies/socket-handler.html#send-bound-limitation)).
694
694
695
695
From this we conclude that offering async functions in traits without *some* solution to the "send bound problem" means it will not be usable for most Rust developers. The Fuchsia case also provides evidence that, even when workarounds exist, they are not obvious to Rust developers.
696
696
@@ -714,7 +714,7 @@ Versus aliases that always bound every method, RTN can be used to
714
714
* bound individual methods
715
715
* introduce bounds for traits other than `Send`.
716
716
717
-
As [described in the motivation](#bounding-specific-methods), bounding individual methods allows for greater reuse.
717
+
As [described in the motivation](#bounding-specific-methods), bounding individual methods allows for greater reuse.
718
718
For functions that only make use of a subset of the methods in a trait, RTN can be used to create a "maximally reusable" signature.
719
719
720
720
## What other syntax options were considered?
@@ -737,7 +737,7 @@ The document reviewed the following designs overall:
* This notation is more concise and feels less heavy-weight. However, we expect users to primarily use aliases; also, the syntax "feels" surprising to many users, since Rust tends to use `..` to indicate elided items. The biggest concern here is a potential future conflict. If we (a) extend the notation to allow argument types to be specified ([as described in the future possibilities section](#future-possibilities)) AND (b) support some kind of variadic arguments, then `D::items()` would most naturally indicate "no arguments".
740
+
* This notation is more concise and feels less heavy-weight. However, we expect users to primarily use aliases; also, the syntax "feels" surprising to many users, since Rust tends to use `..` to indicate elided items. The biggest concern here is a potential future conflict. If we (a) extend the notation to allow argument types to be specified ([as described in the future possibilities section](#future-possibilities)) AND (b) support some kind of variadic arguments, then `D::items()` would most naturally indicate "no arguments".
* This notation avoids looking like a function call. Many team members found it dense and difficult to read. While intended to look more like an associated type, the use of a lower-case keyword still makes it feel like a new thing. The syntax does not support future extensions (e.g., specifying the value of argument types).
743
743
* "Output": `D: Database<items::Output: DoubleEndedIterator>` (see [this blog post](https://smallcultfollowing.com/babysteps/blog/2023/06/12/higher-ranked-projections-send-bound-problem-part-4/) for details)
@@ -747,7 +747,7 @@ We briefly review the key arguments here:
747
747
748
748
## Why not use `typeof`, isn't that more general?
749
749
750
-
The compiler currently supports a `typeof` operation as an experimental feature (never RFC'd). The idea is that `typeof <expr>` type-checks `expr` and evaluates to the result of that expression. Therefore `typeof 22_i32` would be equivalent to `i32`, and `typeof x` would be equivalent to whatever the type of `x` is in that context (or an error if there is no identifier `x` in scope).
750
+
The compiler currently supports a `typeof` operation as an experimental feature (never RFC'd). The idea is that `typeof <expr>` type-checks `expr` and evaluates to the result of that expression. Therefore `typeof 22_i32` would be equivalent to `i32`, and `typeof x` would be equivalent to whatever the type of `x` is in that context (or an error if there is no identifier `x` in scope).
751
751
752
752
It might appear that `typeof` can be used in a similar way to RTN, but in fact it is significantly more complex. Consider our first example, the `HealthCheck` trait:
753
753
@@ -788,7 +788,7 @@ where
788
788
H:HealthCheck+Send+ 'static,
789
789
typeof {
790
790
lethc:&'amutH;
791
-
lets:Server;
791
+
lets:Server;
792
792
H::check(hc, s)
793
793
}:Send,
794
794
```
@@ -935,7 +935,7 @@ Second, it covers far fewer use cases than RTN: it cannot be used to express spe
935
935
936
936
## Why not Send trait transformers?
937
937
938
-
[Trait transformers][] are a proposal to have "modifiers" on trait bounds that produce a derived version of the trait. For example, `T: async Iterator` might mean "T implements a version of `Iterator` where the `next` function is `async`". Following this idea, one can imagine `T: Send HealthCheck` to mean "implement a version of `HealthCheck` where every async fn returns a `Send` future". This idea is an ergonomic way to manage traits that have a lot of async functions, as [came up in the Microsoft case study](https://rust-lang.github.io/async-fundamentals-initiative/evaluation/case-studies/microsoft.html#send-bounds).
938
+
[Trait transformers][] are a proposal to have "modifiers" on trait bounds that produce a derived version of the trait. For example, `T: async Iterator` might mean "T implements a version of `Iterator` where the `next` function is `async`". Following this idea, one can imagine `T: Send HealthCheck` to mean "implement a version of `HealthCheck` where every async fn returns a `Send` future". This idea is an ergonomic way to manage traits that have a lot of async functions, as [came up in the Microsoft case study](https://rust-lang.github.io/async-fundamentals-initiative/evaluation/case-studies/microsoft.html#send-bounds).
@@ -1110,4 +1110,3 @@ We expect to make traits with async functions and RPITIT dyn safe in the future
1110
1110
## Naming the zero-sized types for a method
1111
1111
1112
1112
Every function and method `f` in Rust has a corresponding zero-sized type that uniquely identifies `f`. The RTN notation `T::check(..)` refers to the return value of `check`; conceivably `T::check` (without the parens) could be used to refer the type of `check` itself. In this case, `T::check(..)` can be thought of as shorthand for `<T::check as Fn<_>>::Output`.
0 commit comments