-
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 5 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,195 @@ | ||
| - 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 `Fn`, `FnMut`, and `FnOnce`. | ||
| 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) | ||
| ) { } | ||
| ``` | ||
|
|
||
| ### 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 in the `Fn`, `FnMut` and `FnOnce` traits 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 `Fn`, `FnMut` and `FnOnce` 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 | ||
|
|
||
| The syntax `Fn`, `FnMut` and `FnOnce` traits is currently not documented in the reference. | ||
|
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. Mostly just adding this comment here from Zulip to keep track of some of the discussion. I mentioned that we should probably try to unify the syntax for Particularly, the example you gave that did not compile: trait Test {
fn thing((x, y): (usize, usize));
}seems to directly contradict what the reference says: https://doc.rust-lang.org/stable/reference/items/functions.html In particular, it appears that only
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. We've already briefly discussed this on Zulip but just to get others up to speed: The Reference is correct in stating that the "parameter names" of even bodiless functions are patterns ( The grammar rules describe what's syntactically allowed and doesn't dictate what's semantically allowed or forbidden. Patterns except
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. To further clarify this (since the Reference doesn't document this nuance which I'll rectify in a moment): The parameters of function pointer types basically share the same grammar as parameters of trait associated functions in Rust 2015 where the "parameter name" is optional and where the parameter pattern is restricted. As I've mentioned on Zulip it obeys the following rules (excerpt): RestrictedPat = RestrictedIdent | "&" RestrictedIdent | "&&" RestrictedIdent | "mut" CommonIdent;
RestrictedIdent = CommonIdent | "_" | "false" | "true";In Rust >2015, however, the grammar of trait associated functions is the same as all other kinds of functions (free, foreign, impl associated).
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. In the past
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. I'm currently working on yeeting that. I've seen the historical RFCs, future-incompat issues and relevant PRs. Fun fact, the Back in the day there were 5 argument modes, That's literally the reason why, it's beyond insane. Crater run coming soon btw :)
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.
It is documented. See e.g., https://doc.rust-lang.org/nightly/reference/paths.html#grammar-TypePathFnInputs. More specifically
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. Ahhh that is where it is, I will change the reference level explanation in terms of those rules
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. Addendum: Type paths are not the only paths featuring parenthesized generic argument lists. Expression and pattern paths have the same behavior. For example the following is legal: #[cfg(false)]
fn scope() {
let F::() -> () = F::() -> ()::Y;
}
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. I've rewritten the reference-level explanation in terms of
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. Final addendum: This syntax is obviously not tied to
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.
Is this behaviour documented in the reference anywhere?
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.
I've made this more explicit in the RFC |
||
|
|
||
| Before this RFC, the syntax rules are: | ||
| ```grammar,types | ||
| ImplTraitParen -> `impl` TraitBoundParen | ||
|
|
||
| TraitBoundParen -> | ||
| ( `?` | ForLifetimes )? TypePath TraitBoundParenArgs | ||
| | `(` ( `?` | ForLifetimes )? TypePath TraitBoundParenArgs `)` | ||
|
|
||
| TraitBoundParenArgs -> `(` FunctionParametersNoAttr? `)` BareFunctionReturnType? | ||
|
|
||
| FunctionParametersNoAttr -> | ||
| Type ( `,` Type )* `,`? | ||
|
|
||
| BareFunctionReturnType -> `->` TypeNoBounds | ||
| ``` | ||
|
|
||
| After this RFC, the following rules will change: | ||
|
|
||
| ```grammar,types | ||
| TraitBoundParenArgs -> `(` MaybeNamedFunctionParametersNoAttr? `)` BareFunctionReturnType? | ||
|
|
||
| MaybeNamedFunctionParametersNoAttr -> | ||
| MaybeNamedParamNoAttr ( `,` MaybeNamedParamNoAttr )* `,`? | ||
|
|
||
| MaybeNamedParamNoAttr -> | ||
| ( ( IDENTIFIER | `_` ) `:` )? Type | ||
| ``` | ||
|
|
||
| Note that this means that: | ||
|
|
||
| - 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(#[allow(unused)] msg: String, priority: usize), y: usize) { } | ||
| ``` | ||
| Note that attributes are already allowed on `fn` pointers: | ||
| ```rust | ||
| fn test(x: fn(#[allow(unused)] msg: String, priority: usize), y: usize) { } | ||
| ``` | ||
| The reason why attributes are not allowed is to keep this RFC and the implementation simple, and because I don't see a use for them. | ||
|
JonathanBrouwer marked this conversation as resolved.
Outdated
|
||
|
|
||
| - This syntax does not match that of `fn` pointers exactly. | ||
| For historic reasons, the following `fn` pointer type is allowed: | ||
| ```rust | ||
| #[cfg(false)] | ||
| type T = fn(mut x: (), &x: (), &&x: (), false: (), &_: (), &true: ()); | ||
| ``` | ||
| But this RFC proposes that the following `impl Fn` type is not allowed: | ||
|
|
||
| ```rust | ||
| #[cfg(false)] | ||
| impl Fn(mut x: (), &x: (), &&x: (), false: (), &_: (), &true: ()); | ||
| ``` | ||
|
|
||
| The names of function parameters are limited to ``IDENTIFIER | `_` ``. | ||
| The reason why we don't match the syntax of `fn` pointer types is because the syntax was a historic mistake and we should not repeat that. | ||
|
|
||
| ## 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, as to avoid the historic mistakes mentioned in the [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 | ||
|
JonathanBrouwer marked this conversation as resolved.
Outdated
|
||
| * 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.