-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stabilize return type notation (RFC 3654) #138424
base: master
Are you sure you want to change the base?
Stabilize return type notation (RFC 3654) #138424
Conversation
r? @wesleywiser rustbot has assigned @wesleywiser. Use |
cc @rust-lang/lang and @rust-lang/types (though this PR doesn't do that much interesting on the types side other than giving us a new way of naming RPITITs). |
cc @rust-lang/rust-analyzer |
This comment has been minimized.
This comment has been minimized.
31739fb
to
f5b0f69
Compare
Of note, the return type notation for specifying the Future in the AsyncFn traits doesn't seem to have been implemented yet. https://rust-lang.github.io/rfcs/3668-async-closures.html#interaction-with-return-type-notation-naming-the-future-returned-by-calling |
Yeah that will need to get done later as its semantics are quite different. I'll put it in the report. |
Man, that syntax is not great. Really gives ammo to people who are like "rust is a pile of confusing sigils". Which isn't really a good reason to reject something, but I'd rather we had a more generally applicable and less confusing syntax for Particularly given relative rarity of cases where one would see or use this syntax, I think using something that looks like a valid UFCS call expression will be a perpetually renewing source of confusion amongst the community. It's also rather weird, given the fact that Fn traits more or less already have an assoc type for the output. |
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.RTN cannot be used to bound the output futures from calling
AsyncFn*
traits yet. This requires some design work. See: https://rust-lang.github.io/rfcs/3668-async-closures.html#interaction-with-return-type-notation-naming-the-future-returned-by-callingImplementation 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