Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-464 · Case file with fixtures · Case 436 of 694 · Compiler evidence

Rust Function Parameter Names Must Be Unique

Each function parameter must create an unambiguous local binding. Rename parameters by role, use underscore for intentionally unused inputs, and review call-site order when duplicate names reveal an API design problem.

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
Function parameters are simultaneous bindings in one lexical scope, and Rust does not select or shadow one duplicate name.
First discriminating check
Rename inputs by semantic role, review positional call-site risks, and use standalone underscore only for values that intentionally need no binding.

I declared fn merge(value: u8, value: u8) and Rust reported E0415 because value is bound twice in one parameter list.

The failing fixture exposes an ambiguity that other languages sometimes resolve by shadowing. In Rust, both arguments are simultaneously inputs to the body, so one local name cannot identify both.

Parameters are bindings in one scope

Each named parameter introduces a local variable for the function body. The function parameter Reference describes parameters as irrefutable patterns paired with types.

The identifier-pattern rules require a binding name to be unique inside its pattern context. E0415 applies that clarity to the full parameter list.

Rust does not choose the last parameter or silently hide the first.

Name roles, not repeated primitive types

The repaired fixture uses left and right and combines them with saturating_add.

Role names become more important when arguments share a type. A signature such as copy(source: PathBuf, destination: PathBuf) is safer to read than copy(path1, path2), even though callers still pass positional arguments.

The duplicate-name error may therefore reveal that the API's concepts were never distinguished clearly.

Check whether parameter order is easy to reverse

Two u64 arguments called start and end can still be swapped by callers. After renaming, I consider a range type, options struct, or domain newtypes when reversal would be costly.

I do not create wrappers for every pair of integers, but I use them for identifiers, money, offsets, and units whose primitive equality hides semantic differences.

E0415 fixes body scope; API design must also protect call sites.

Use underscore for intentionally unused inputs

Trait implementations and callbacks sometimes receive an argument that a particular body does not need. I can name it _context to document its role while suppressing the unused warning, or use _ when no local name is required.

I do not give two unused parameters the same underscore-prefixed name. _left is still a binding; only the standalone wildcard _ binds nothing and may repeat.

That distinction is useful in generated adapter functions.

Destructured parameters need unique inner bindings

Since parameters can use irrefutable patterns, duplicates may be nested:

fn area((width, height): (u32, u32)) -> u32 { /* ... */ }

Every binding introduced across the parameter list enters the body scope. I expand shorthand and inspect nested tuple or struct patterns when the reported duplicate is not obvious.

For public functions, simple named parameters are often easier for documentation than heavy destructuring at the boundary.

Generated signatures need schema identity

Code generators may derive parameter names from fields that normalise to the same Rust identifier, such as user-id and user_id. Appending arbitrary numbers makes code compile but can hide the source collision.

I detect duplicate normalised names during generation and report both original schema paths. If both inputs remain meaningful, stable role-based disambiguation should be part of the generator contract.

This creates better errors than waiting for rustc deep inside generated output.

Renaming can reveal a logic mistake

In the failing function, returning value never stated which argument should win. Once names become left and right, the missing merge rule is obvious.

I review the body after any duplicate-parameter repair rather than only selecting a name that compiles. Tests should use unequal inputs, because equal examples cannot reveal which position is read.

My E0415 checklist

At FFI and generated boundaries, parameter names may not affect the binary calling convention, but they still affect headers, documentation, bindings, and audits. I keep the names stable and semantic even when rustc could accept _ for every input. When the external schema itself has duplicates or invalid identifiers, I preserve the original name in generated comments or metadata and use a deterministic Rust-safe mapping. This makes later schema comparisons possible instead of losing provenance during compilation repair.

  • Which parameters introduce the same local binding?
  • Was one name copied when a different semantic role was intended?
  • Do same-typed positional arguments need stronger API structure?
  • Is an intentionally unused input better written _ or _role?
  • Does nested parameter destructuring hide the duplicate?
  • Did schema name normalisation create a generated collision?
  • Do tests use distinct values to detect swapped or ignored inputs?
  • Does the body define how both arguments participate?

The core principle is that function inputs share one lexical scope and each meaningful input needs one identity. I use the compiler error to clarify roles in both the body and the public calling contract, not merely to invent a second spelling.