Skip to content

Commit

Permalink
Prepare RFC 3654 to be merged
Browse files Browse the repository at this point in the history
We've been using "tracking issue" rather than "rust issue" recently,
so let's adjust that.  Let's also use sentence case for the title and
remove some remaining bits left over from the RFC template.  And let's
replace some Unicode characters that don't have ubiquitous font
support with ASCII equivalents.
  • Loading branch information
traviscross committed Jul 12, 2024
1 parent d01b3f2 commit 3504c71
Showing 1 changed file with 12 additions and 37 deletions.
49 changes: 12 additions & 37 deletions text/3654-return-type-notation.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Return Type Notation in Bounds and Where-clauses
# Return type notation (RTN) in bounds and where-clauses

- Feature Name: `return_type_notation`
- Start Date: 2024-06-04
- RFC PR: [rust-lang/rfcs#3654](https://github.com/rust-lang/rfcs/pull/3654)
- Rust Issue: [rust-lang/rust#109417](https://github.com/rust-lang/rust/issues/109417)
- Tracking Issue: [rust-lang/rust#109417](https://github.com/rust-lang/rust/issues/109417)

# Summary
[summary]: #summary
Expand All @@ -30,8 +30,6 @@ Examples of RTN usage allowed by this RFC include:
# Motivation
[motivation]: #motivation

> Why are we doing this? What use cases does it support? What is the expected outcome?
Rust now supports async fns and `-> impl Trait` in traits (acronymized as AFIT and RPITIT, respectively), but we currently lack the ability for users to declare additional bounds on the values returned by such functions. This is often referred to as the [Send bound problem][sbp], because the most acute manifestation is the inability to require that an `async fn` returns a `Send` future, but it is actually more general than both async fns and the `Send` trait (as discussed below).

[sbp]: https://smallcultfollowing.com/babysteps/blog/2023/02/01/async-trait-send-bounds-part-1-intro/
Expand Down Expand Up @@ -177,8 +175,8 @@ where
S: Service<
(),
Response: Send,
call(..): Send, // 👈 "the method `call`
// returns a `Send` future"
// "The method `call` returns a `Send` future."
call(..): Send,
> + Send + 'static,
{
tokio::spawn(async move {
Expand Down Expand Up @@ -213,7 +211,7 @@ where
{
fn widgets(&self) -> impl Iterator<Item = Widget> {
self.factory.widgets().rev()
// 👆 requires that the iterator be double-ended
// ^^^ requires that the iterator be double-ended
}
}
```
Expand Down Expand Up @@ -269,7 +267,7 @@ The function `spawn_call` can then be written as follows:
async fn spawn_call<S>(service: S) -> S::Response
where
S: SendService<(), Response: Send> + 'static,
// 👆 use the alias
// ^^^^^^^^^^^ use the alias
{
tokio::spawn(async move {
service.call(()).await // <--- OK!
Expand Down Expand Up @@ -306,8 +304,6 @@ While `SendBackend` may be convenient most of the time, it is also stricter than
# Guide-level explanation
[guide-level explanation]: #guide-level-explanation

> Explain the proposal as if it was already included in the language and you were teaching it to another Rust programmer.
Async functions can be used in many ways. The most common configuration is to use a *work stealing* setup, in which spawned tasks may migrate between threads. In this case, all futures have to be `Send` to ensure that this migration is safe. But many applications prefer to use a *thread-per-core* setup, in which tasks, once spawned, never move to another thread (one important special case is where the entire application runs on a single thread to begin with, common in embedded environments but also in e.g. Google's Fuchsia operating system).

For the most part, async functions today do not declare whether they are `Send` explicitly. Instead, when a future `F` is spawned on a multithreaded executor, the compiler determines whether it implements `Send`. So long as `F` results from an `async fn` that only calls other `async fn`s, the compiler can analyze the full range of possible executions. But there are limitations, especially around calls to async trait methods like `f.method()`. If the type of `f` is either a generic type or a `dyn` trait, the compiler cannot determine which impl will be used and hence cannot analyze the function body to see if it is `Send`. This can result in compilation errors.
Expand Down Expand Up @@ -492,14 +488,6 @@ When using a trait `MyTrait` that defines a sendable alias `SendMyTrait`...
# Reference-level explanation
[reference-level explanation]: #reference-level-explanation

>This is the technical portion of the RFC. Explain the design in sufficient detail that:
>
>- Its interaction with other features is clear.
>- It is reasonably clear how the feature would be implemented.
>- Corner cases are dissected by example.
>
>The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work.
## Background and running examples

### The `Widgets` trait
Expand Down Expand Up @@ -543,8 +531,8 @@ Type = i32
| Type "::" AssociatedTypeName
| "<" Type as TraitName Generics? ">" "::" AssociatedTypeName
| ...
| Type "::" MethodName "(" ".." ")" // 👈 new
| "<" Type as TraitName Generics? ">" "::" MethodName "(" ".." ")" // 👈 new
| Type "::" MethodName "(" ".." ")" // <--- new
| "<" Type as TraitName Generics? ">" "::" MethodName "(" ".." ")" // <--- new
Generics = "<" Generic,* ">"
Generic = Type | Lifetime | ...
Expand All @@ -566,7 +554,7 @@ TraitRef = TraitName "<" Generic,* AssociatedBound ">"
AssociatedBound = Identifier "=" Generic
| Identifier ":" TraitRef // (from RFC #2289)
| Identifier "(" ".." ")" ":" TraitRef // 👈 new
| Identifier "(" ".." ")" ":" TraitRef // <--- new
```

Examples: given the `Widgets` trait defined earlier in this section...
Expand All @@ -588,7 +576,7 @@ Although RTN types extend the type grammar, the compiler will not allow them to
* `dyn Widgets<widgets(..): Send>`
* `impl Widgets<widgets(..): Send>`

> *Nonnormative:* The current set of allowed locations correspond to places where generics on the method (e.g., `widgets(..)`) can be converted into higher-ranked trait bounds, as described in the next section. We expect [future RFCs](#future-possibilities) to extend the places where RTN can appear. These RFCs will detail how to manage generic parameters in those functions. The expectation is that the behavior will generally match "whatever `'_` would do". For example, `let w: W::widgets(..) = ...` would be equivalent to `let w: W::$Widgets<'_> = ...`.
> *Non-normative:* The current set of allowed locations correspond to places where generics on the method (e.g., `widgets(..)`) can be converted into higher-ranked trait bounds, as described in the next section. We expect [future RFCs](#future-possibilities) to extend the places where RTN can appear. These RFCs will detail how to manage generic parameters in those functions. The expectation is that the behavior will generally match "whatever `'_` would do". For example, `let w: W::widgets(..) = ...` would be equivalent to `let w: W::$Widgets<'_> = ...`.
## Converting to higher-ranked trait bounds

Expand Down Expand Up @@ -626,8 +614,6 @@ Although conceptually RTN could be used for any trait method, we choose to limit
# Drawbacks
[drawbacks]: #drawbacks

>Why should we *not* do this?
## Confusion about future type vs awaited type

When writing an async function, the future is implicit:
Expand Down Expand Up @@ -663,11 +649,6 @@ It is a consequence of existing precedent:
# Rationale and alternatives
[rationale and alternatives]: #rationale-and-alternatives

>- Why is this design the best in the space of possible designs?
>- What other designs have been considered and what is the rationale for not choosing them?
>- What is the impact of not doing this?
>- If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain?
## What is the impact of not doing this?

The Async Working Group has performed [five case studies][cs] around the use of async functions in trait, covering usage in the following scenarios:
Expand Down Expand Up @@ -763,7 +744,7 @@ and a function bounding it
fn start_health_check<H>(health_check: H, server: Server)
where
H: HealthCheck + Send + 'static,
H::check(..): Send, // 👈 How would we write this with `typeof`?
H::check(..): Send, // <--- How would we write this with `typeof`?
```

To write the above with `typeof`, you would do something like this
Expand Down Expand Up @@ -821,7 +802,7 @@ might have been desugared as follows:

```rust
trait Factory {
type widgets<'a>: Iterator<Item = Widget>; // 👈 implicitly introduced
type widgets<'a>: Iterator<Item = Widget>; // <--- implicitly introduced
fn widgets(&self) -> Self::widgets<'_>;
}
```
Expand Down Expand Up @@ -951,8 +932,6 @@ C++ has [`decltype`](https://en.cppreference.com/w/cpp/language/decltype) expres
# Unresolved questions
[unresolved questions]: #unresolved-questions

>- What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
## Does stabilizing `T::foo(..)` notation as a standalone type create a confusing inconsistency with `-> ()` shorthand?

Unlike a regular associated type, this RFC does not allow a trait bound that specifies the return type of a method, only the ability to put bounds on that return type.
Expand All @@ -965,10 +944,6 @@ Prior to stabilizing the "associated type position" syntax, we should be sure we
# Future possibilities
[future possibilities]: #future-possibilities

>Think about what the natural extension and evolution of your proposal would
be and how it would affect the language and project as a whole in a holistic
way.

## Implementing trait aliases

Referring to the `Service` trait specifically,
Expand Down

0 comments on commit 3504c71

Please sign in to comment.