Stabilize return type notation (RFC 3654) #138424
Open
+301
−607
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Return Type Notation (RTN) Stabilization Report
Stabilization summary
This PR stabilizes return-type notation in where clause and item bound position, both in path and associated type bound forms for methods in traits that have lifetime generics.
This gives users a way to add trait bounds to the anonymous generic associated types (GATs) that are generated from the return-position impl trait in trait (RPITIT) desugaring.
Motivation
Rust now supports AFITs and RPITITs, but we currently lack the ability for users to declare bounds on the anonymous types returned by such functions at the usage site. This is often referred to as the Send bound problem, since it commonly affects futures which may only conditionally be
Send
depending on the executor that they are involved with.Return type notation gives users an intuitive way to solve this problem. See the alternatives section of the RFC for other proposed solutions.
This feature makes AFIT more useful in APIs, where users can now bound their methods appropriately at the usage site rather than having to provide a constrained definition like
-> impl Future<Output = T> + Send
within the trait.Major design decisions since the RFC
There were no major changes to the design, but there was some consideration whether the bound should be spelled with
(..)
or()
. The implementation commits to(..)
since that saves space for explicitly specified parameters in the future likeT::method(i32): Trait
which can be used to only bound subsets of the RTN type.What is stabilized?
As mentioned in the summary, RTN is allowed as the self type of a where clause anywhere that a where clause is allowed. This includes shorthand syntax, like
where T::method(..): Send
, where the trait is not specified, in the same situations that it would be allowed for associated types.RTN is also allowed in associated type bound position, and is allowed anywhere an associated type bound is allowed, except for
dyn Trait
types.Linting
This PR also removes the
async_fn_in_trait
lint. This lint was introduced to discourage the usage ofasync fn
in trait since users were not able to addwhere
clause bounds on the futures that these methods returned.This is now possible for functions that have only lifetime generics. The only caveat here is that it's still not possible for functions with type or const generics. However, I think that we should accept that corner case, since the intention was to discourage the usage before RTN was introduced, and not to outright ban AFIT usage. See #116184 for some context.
I could change my mind on this part, but it would still be quite annoying to have to keep this lint around until we fix RTN support for methods with type or const generics.
What isn't stabilized? (a.k.a. potential future work)
RTN is not allowed as a free-standing type. This means that it cannot be used in ADTs (structs/enums/unions), parameters,
let
statements, or turbofishes.RTN can only be used when the RPITIT (return-position impl trait in trait) is the outermost type of the return type, i.e.
-> impl Sized
but not-> Vec<impl Sized>
. It may also be used for AFIT (async fn in trait), since that desugars to-> impl Future<Output = T>
.RTN is only allowed for methods with only lifetimes. Since the RTN syntax expands to a
for<...>
binder of all of the generics of the associated function, and we do not have robust support for non-lifetime binders yet, we reject the case where we would be generating afor<T>
binder for now. This restriction may be lifted in the future.Implementation summary
This feature is generally a straightforward extension of associated types and associated type bounds, but adjusted (in the presence of
(..)
-style generics) to do associated item resolution in the value namespace to look up an associated function instead of a type.In the resolver, if we detect a path that ends in
(..)
, we will attempt to resolve the path in the value namespace:rust/compiler/rustc_resolve/src/late.rs
Lines 800 to 810 in 30f168e
In "resolve bound vars", which is the part of the compiler which is responsible for resolving lifetimes in HIR, we will extend the (explicit or implicit)
for<>
binder of the clause with any lifetimes params from the method:rust/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
Lines 2002 to 2019 in 30f168e
We similarly handle RTN bounds in associated type bounds:
rust/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
Lines 1792 to 1804 in 30f168e
In HIR lowering, we intercept the type being lowered if it's in a where clause:
rust/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs
Lines 434 to 436 in 30f168e
Otherwise, we unconditionally reject the RTN type, mentioning that it's not allowed in arbitrary type position. This can be extended later to handle the lowering behavior of other positions, such as structs and unsafe binders.
The rest of the compiler is already well equipped to deal with RPITITs, so not very much else needed to be changed.
Tests
"Positive tests":
tests/ui/associated-type-bounds/return-type-notation/basic.rs
...
syntax:tests/ui/associated-type-bounds/return-type-notation/namespace-conflict.rs
.tests/ui/async-await/return-type-notation/supertrait-bound.rs
.tests/ui/associated-type-bounds/return-type-notation/path-works.rs
.tests/ui/associated-type-bounds/implied-from-self-where-clause.rs
.tests/ui/associated-type-bounds/return-type-notation/higher-ranked-bound-works.rs
."Negative tests":
tests/ui/associated-type-bounds/return-type-notation/bad-inputs-and-output.rs
.tests/ui/associated-type-bounds/return-type-notation/bare-path.rs
.tests/ui/associated-type-bounds/return-type-notation/equality.rs
.tests/ui/associated-type-bounds/return-type-notation/non-rpitit.rs
+tests/ui/associated-type-bounds/return-type-notation/not-a-method.rs
.tests/ui/async-await/return-type-notation/super-method-bound-ambig.rs
.tests/ui/associated-type-bounds/return-type-notation/path-ambiguous.rs
.tests/ui/async-await/return-type-notation/ty-or-ct-params.rs
.Remaining bugs, FIXMEs, and open issues
What other unstable features may be exposed by this feature?
This feature gives users a way to name the RPIT of a method defined in an impl, but only in where clauses.
Tooling support
Rustfmt: ✅ Supports formatting
(..)
-style generics.Clippy: ✅ No support needed, unless specific clippy lints are impl'd to care for RTN specifically.
Rustdoc: ❓ #137956 should fix support for RTN locally. Cross-crate re-exports are still broken, but that part of rustdoc (afaict) needs to be overhauled in general.
Rust-analyzer: ❓ There is parser support. I filed an issue for improving support in the IDE in rust-lang/rust-analyzer#19303.
History
RTN was first implemented in associated type bounds in #109010.
It was then implemented in
where T::method(..): Send
style bounds in #129629.The RFC was proposed in rust-lang/rfcs#3654.
There was a call for testing here: https://blog.rust-lang.org/inside-rust/2024/09/26/rtn-call-for-testing.html.
History
return_type_notation
is enabled #115624resolve_bound_vars
#132047Acknowledgments
Thanks to @nikomatsakis for writing the RFC for RTN, and to various reviewers for reviewing the various PRs implementing parts of this feature.
Pending items before stabilization