RFA-560 · Case file with fixtures · Case 532 of 694 · Compiler evidence
Rust Function Pointer Parameter Names Cannot Carry Binding Patterns
Parameter mutation belongs to a function body and its local binding. Function pointer types record input types, not implementation-local binding modes.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all Rust targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- Implementation-local binding behaviour has been placed in a type that records only values crossing the call boundary.
- First discriminating check
- Keep only parameter types or simple names in the pointer alias and put mutability or destructuring in each concrete function definition.
A function definition owns local parameter bindings. A function pointer type does not. It describes how a callable is invoked, so adding mut to a parameter name inside the type alias produces E0561.
The failing fixture declares type Update = fn(mut value: u32) -> u32. The word mut is a pattern modifier for the implementation's local variable, not part of the calling contract.
The ABI sees a u32, not a mutable local name
Callers pass a u32 either way. Whether an implementation binds that incoming value as value, mut value, or _ does not change parameter layout, ownership transfer, or result type.
A function pointer therefore records fn(u32) -> u32. Optional parameter names can improve documentation, but they do not create local variables shared by every compatible function.
The official E0561 page permits identifiers, _, or omitted names and rejects patterns such as mut value and reference destructuring.
The implementation chooses its binding pattern
The repaired fixture uses fn(value: u32) -> u32 in the alias. The concrete increment function writes mut value because its body reassigns the local copy.
This separation is clean:
- the alias promises one owned integer input and one integer output;
- the function body decides how to work with its local binding;
- assignment to that local does not mutate the caller's original integer.
The Reference documents function parameters as irrefutable patterns in function definitions. A pointer type has a narrower descriptive role.
Mut on a binding differs from an &mut parameter type
mut value: u32 means the callee may rebind its owned local variable. value: &mut u32 means the caller lends exclusive access to an existing integer. These are different public contracts.
Changing the pointer alias to fn(&mut u32) just because the implementation wants a mutable local would incorrectly require caller-owned mutation. The original by-value signature needs only a mutable binding inside the function.
I read mut before a name as local binding permission and &mut inside the type as borrowed access permission.
Destructuring belongs in the function definition or body
A concrete function can destructure tuple or struct inputs when its parameter pattern is allowed, or it can accept one name and destructure with let inside. The pointer type continues to name the complete input type.
Keeping destructuring out of the alias also lets several compatible implementations choose different local names and processing shapes.
The Reference's function pointer type section specifies the type grammar, including qualifiers, parameter types, and variadics for supported ABIs.
Function items coerce to compatible pointers
Each named function has its own zero-sized function-item type. It can coerce to a function pointer when safety, ABI, parameter, and return types agree. Local parameter names and mut binding choices are not part of this comparison.
Closures without captures can also coerce to function pointers under the documented conditions. Capturing closures need generic Fn* bounds or trait objects because they carry an environment.
Type aliases should describe domain roles carefully
Naming a pointer Update can imply mutation even when the function takes and returns values. I may prefer Transform for fn(T) -> T and reserve Update for fn(&mut T).
Parameter names in the alias can document roles, but rustdoc and callers rely more strongly on the alias name, input types, and surrounding documentation. Binding modifiers cannot carry that meaning.
I also test the alias with at least two compatible functions. This proves that implementation-local names and mutation choices can differ while the call contract remains identical.
My E0561 checklist
- Is the syntax inside a function definition or a function pointer type?
- Did I place
mutor destructuring in the type alias? - Does mutation concern a local owned binding or caller-owned data?
- Should the public type be
Tor&mut T? - Can the implementation perform destructuring inside its own body?
- Do safety and ABI qualifiers also match the intended callable?
- Is a capturing closure being confused with a function pointer?
- Does the alias name accurately describe value transformation versus mutation?
The core principle is that a callable type describes what crosses the call boundary. Local binding patterns belong to the implementation that receives those values.