-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Named Fn trait parameters
#3955
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
base: master
Are you sure you want to change the base?
Changes from all commits
58f80f2
d756ee5
2e51ecc
caff5dc
93db0a7
dfc02d2
12227d9
8e2948e
036daff
2c76a45
15d8dc3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| - Feature Name: `named_fn_trait_parameters` | ||
| - Start Date: 2026-04-24 | ||
| - RFC PR: [rust-lang/rfcs#3955](https://github.com/rust-lang/rfcs/pull/3955) | ||
| - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
|
||
| ## Summary | ||
| [summary]: #summary | ||
|
|
||
| Allow (optional) named function parameters in parenthesized generic argument lists, such as those of `Fn`, `FnMut`, `FnOnce`, `AsyncFn`, `AsyncFnMut`, and `AsyncFnOnce`. | ||
| For example: | ||
| ```rust | ||
| fn parse_my_data( | ||
| data: &str, | ||
| log: impl Fn(msg: String, priority: usize) | ||
| ) { } | ||
| ``` | ||
| Similar to named function pointer parameters, these names don't affect rust's semantics. | ||
|
|
||
| ## Motivation | ||
| [motivation]: #motivation | ||
|
|
||
| ### Benefit: Better documentation | ||
|
JonathanBrouwer marked this conversation as resolved.
|
||
|
|
||
| This allows users to better document the meaning of parameters in signatures. This is the primary benefit of this RFC. | ||
|
|
||
| For example, it is not immediately clear what the `String` and `usize` refer to in the type of `log`, providing names like in the example above is much clearer. | ||
|
|
||
| ```rust | ||
| fn parse_my_data( | ||
| data: &str, | ||
| log: impl Fn(String, usize) | ||
| ) { } | ||
| ``` | ||
|
|
||
| The parameter names should also show up on rustdoc. | ||
|
|
||
| ### Benefit: Better LSP hints | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A major reason for this RFC is better documentation and better LSP hints. As a member of T-rust-analyzer, I unfortunately have to inform you that rust-analyzer cannot support this feature, at least not without significant changes (it does not show hints for named It might be possible, though, to support a few special cases.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, sucks :c I still believe this RFC is valuable even without this benefit though, as mentioned the primary benefit is "Better documentation"
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea I think the main issue is that even if the type IR encodes this info, we might lose this information after inference depending on how the types are unified, trait bounds are proven etc
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That said, even though R-A won't necessarily be able to provide inlay hints, the hover-rustdoc could show this information, meaning that it technically still could have some usefulness in the LSP implementation, if only indirectly.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
when unifying two types, couldn't rust-analyzer try to propagate argument names if one type has them and the other doesn't? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @programmerjake We probably could, but that depends on the solver allowing us to encode this info (which is a rustc crate and perf-sensitive, and also a major change). |
||
|
|
||
| When calling `log` in the body of `parse_my_data`, the LSP can provide the function parameter names as "inlay parameter name hints": | ||
| log(`data: `"Message".to_string(), `priority: `1); | ||
|
|
||
| This is a concrete advantage of this approach over using comments to do the same thing, such as in: | ||
| ```rust | ||
| fn parse_my_data( | ||
| data: &str, | ||
| log: impl Fn(/* msg */ String, /* priority */ usize) | ||
| ) { } | ||
| ``` | ||
|
|
||
| ### Benefit: Better consistency with `fn` pointers | ||
|
|
||
| Imagine if `parse_my_data` looked like this: | ||
| ```rust | ||
| fn parse_my_data( | ||
| data: &str, | ||
| log: fn(msg: String, priority: usize) | ||
| ) { } | ||
| ``` | ||
|
|
||
| If due to new requirements the user decides that `impl Fn` suits the usecase better, having to remove the parameter names is unintuive. | ||
| This RFC removes this problem. | ||
|
|
||
| Note that the syntax for this feature does not exactly match that of `fn` pointers, see the [reference level explanation](#reference-level-explanation). | ||
|
|
||
| ## Guide-level explanation | ||
| [guide-level-explanation]: #guide-level-explanation | ||
|
|
||
| You can give names to parameters to the `Fn` trait and its friends to better document the meaning of these parameters, to help people who call your function. | ||
| These names are optional and don't have any semantic meaning. Named and unnamed parameters can be mixed, for example: | ||
|
|
||
| ```rust | ||
| fn parse_my_data( | ||
| data: &str, | ||
| log: impl Fn(String, priority: usize) | ||
| ) { } | ||
| ``` | ||
|
|
||
| This same syntax also applies to trait bounds, for example: | ||
| ```rust | ||
| fn parse_my_data< | ||
| L: Fn(msg: String, priority: usize) | ||
| >( | ||
| data: &str, | ||
| log: L | ||
| ) { } | ||
| ``` | ||
|
|
||
| ## Reference-level explanation | ||
| [reference-level-explanation]: #reference-level-explanation | ||
|
|
||
| Before this RFC, the syntax rules of parenthesized generic argument lists are: | ||
| ```grammar,types | ||
| TypePathFn -> `(` TypePathFnInputs? `)` (`->` TypeNoBounds)? | ||
|
|
||
| TypePathFnInputs -> Type (`,` Type)* `,`? | ||
| ``` | ||
|
|
||
| After this RFC, the `TypePathFnInputs` rule will be replaced by: | ||
|
|
||
| ```grammar,types | ||
| TypePathFnInputs -> TypePathFnInput (`,` TypePathFnInput)* `,`? | ||
|
|
||
| TypePathFnInput -> ( ( IDENTIFIER | `_` ) `:` )? Type | ||
| ``` | ||
|
|
||
| Below are two chapters on some design tradeoffs made here. | ||
|
|
||
| ### Attributes are not allowed on parenthesized generic argument lists | ||
| Attributes are not allowed on parameters of these traits. This remains unchanged from the current situation. The following will not work: | ||
| ```rust | ||
| fn test(x: impl Fn(#[cfg(...)] msg: String, priority: usize), y: usize) { } | ||
| ``` | ||
| Note that attributes are already allowed on `fn` pointers: | ||
| ```rust | ||
| fn test(x: fn(#[cfg(...)] msg: String, priority: usize), y: usize) { } | ||
| ``` | ||
| The reason why attributes are not allowed is to keep this RFC and the implementation simple. | ||
| Allowing attributes such as `#[cfg(...)]` could be useful, but is out of scope for this RFC. | ||
| This choice is the most safe and conservative choice, attributes could be allowed in the future. | ||
|
|
||
| ### Patterns are not allowed in parenthesized generic argument lists | ||
| This syntax is not consistent with other features in the language. | ||
| The names of function parameters are limited to ``IDENTIFIER | `_` ``. | ||
| This choice is made because it is the most safe and conservative choice, keeping the option open to allow patterns in the future if desired. | ||
| Below is a comparison with two other language features: | ||
|
|
||
| #### `fn` pointers | ||
| This is unlike `fn` pointers, which allows a `RestrictedPat` (and then semantically rejects anything other than identifiers). | ||
| Therefore, the following program compiles: | ||
| ```rust | ||
| #[cfg(false)] | ||
| type F = fn(mut x: (), &x: (), &&x: (), false: (), &_: (), &true: ()); | ||
| ``` | ||
| ``` | ||
| RestrictedPat = Ident | "&" Ident | "&&" Ident | "mut" CommonIdent; | ||
| Ident = CommonIdent | ReservedIdent (* includes `_`, `false`, `true` *) | ||
| ``` | ||
|
|
||
| #### trait functions without bodies | ||
| This is also unlike trait functions without bodies. Arbitrary patterns are allowed (and then semantically anything other than identifiers is rejected). | ||
| Therefore, the following program compiles: | ||
| ```rust | ||
| #[cfg(false)] | ||
| trait Test { | ||
| fn x((x, y): usize); | ||
| } | ||
| ``` | ||
|
|
||
| ## Drawbacks | ||
| [drawbacks]: #drawbacks | ||
|
|
||
| * This makes the syntax of `impl Fn` and friends slightly more complicated | ||
| * This keeps the syntax of `impl Fn` and friends inconsistent with that of `fn` pointers nor functions in trait definitions, for the reasoning about this see [reference level explanation](#reference-level-explanation). | ||
|
|
||
| ## Rationale and alternatives | ||
| [rationale-and-alternatives]: #rationale-and-alternatives | ||
|
|
||
| * An alternative would be to match the `fn` pointer syntax perfectly. This would make the implementation more complicated, without much benefit other than consistency. | ||
| * This needs to be implemented in the language, it cannot be provided by a macro or library as it affects syntactic sugar of the language itself. | ||
| * This makes Rust code easier to read, as it adds better ways to document function signatures. | ||
|
|
||
| ## Prior art | ||
| [prior-art]: #prior-art | ||
|
|
||
| In Rust, this is already allowed in `fn` pointers: | ||
| ```rust | ||
| type LogFunction = fn(msg: String, priority: usize); | ||
| ``` | ||
|
|
||
| In TypeScript: | ||
| ```ts | ||
| type LogFunction = (msg: string, priority: number) => void; | ||
| ``` | ||
|
|
||
| In Kotlin: | ||
| ```kotlin | ||
| fun log(data: String, logFunction: (msg: String, priority: Int) -> Unit) { } | ||
| ``` | ||
|
|
||
| ## Unresolved questions | ||
| [unresolved-questions]: #unresolved-questions | ||
|
|
||
| * Should duplicate parameter names be allowed in named fn trait arguments? This is currently allowed for `fn` pointers and other functions without an accompanying `Body`. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels like something that, even if we allow, we should lint against. It feels reasonable to not propose as part of the RFC, but I feel like what makes the most sense is to replicate what traits/functions do but allow for a deny-by-default lint in the future.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Old issue for this - rust-lang/rust#33995. |
||
| ```rust | ||
| type T = fn(x: usize, x: usize); | ||
| ``` | ||
| ```rust | ||
| trait Test { | ||
| fn thing(x: usize, x: usize); | ||
| } | ||
| ``` | ||
|
|
||
| ## Future possibilities | ||
| [future-possibilities]: #future-possibilities | ||
|
|
||
| * We could allow attributes on `impl Fn` parameters in the future. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.