Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions text/3955-named-fn-trait-parameters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
- 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)
Comment thread
JonathanBrouwer marked this conversation as resolved.
) { }
```
Similar to named function pointer parameters, these names don't affect rust's semantics.

## Motivation
[motivation]: #motivation

### Benefit: Better documentation
Comment thread
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

@ChayimFriedman2 ChayimFriedman2 Apr 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 fn pointers parameters, either).

It might be possible, though, to support a few special cases.

View changes since the review

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The 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"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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

when unifying two types, couldn't rust-analyzer try to propagate argument names if one type has them and the other doesn't?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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 unintuitive.
This RFC removes this problem.

## 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
GenericArgs ->
`<` GenericArgList? `>`
| `(` TypeList? `)` ( `->` TypeNoBounds )?

TypeList ->
`(` Type `,` `)`* Type `,`?
```

After this RFC, these rules will be replaced by:

```grammar,types
GenericArgs ->
`<` GenericArgList? `>`
| `(` MaybeNamedFunctionParameters? `)` ( `->` TypeNoBounds )?

MaybeNamedFunctionParameters →
`(` MaybeNamedParam `,` `)`* MaybeNamedParam `,`?

MaybeNamedParam →
OuterAttribute* (RestrictedPat `:`)? Type
```

Below are two chapters on some design tradeoffs made here.

### Attributes are allowed on parenthesized generic argument lists
Attributes are allowed on parameters in parenthesized generic argument lists:
```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) { }
```

### Restricted patterns are syntactically but not semantically allowed in parenthesized generic argument lists
This syntax is consistent with that of function pointers.
Semantically, the names of function parameters are limited to ``IDENTIFIER | `_` ``.
Below is a comparison with two other language features:

#### Parameters of `fn` pointers and `Fn` trait
Like on `fn` pointers, a `RestrictedPat` is syntactically allowed.
Therefore, the following program compiles:
```rust
#[cfg(false)]
type F = fn(mut x: (), &x: (), &&x: (), false: (), &_: (), &true: ());
```
```
RestrictedPat ->
`mut`? IDENTIFIER
| ( `&` | `&&` )? ( `_` | `false` | `true` | IDENTIFIER )
```

#### trait functions without bodies
For comparison, trait functions without bodies are more permissive than this. Arbitrary patterns are allowed (and then semantically still anything other than identifiers is rejected).
Therefore, the following program compiles:
```rust
#[cfg(false)]
trait Test {
fn x((x, y): usize);
}
```

## Drawbacks
[drawbacks]: #drawbacks
Comment thread
JonathanBrouwer marked this conversation as resolved.

* 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 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

* 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`.

@clarfonthey clarfonthey Apr 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 should figure out how this feature interacts with the "named arguments" feature.
One proposal is to mirror whatever solution we come up with for function pointers.
For example, if named arguments used the syntax `fn f(pub a: T, pub b: U) -> R`, the function trait should be `Fn(pub a: T, pub b: U) -> R`.
Comment thread
JonathanBrouwer marked this conversation as resolved.
* Similarly, we should figure out how this feature interacts with the "defaulted arguments" feature.
Again, this should mirror function pointers.
For example, if default arguments used the syntax `fn(x: String, y: i32 = 0)`, the function trait should be `Fn(x: String, y: i32 = 0)`.