Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-595 · Case file with fixtures · Case 567 of 694 · Compiler evidence

Rust Trait Method Declarations Use Parameter Names, Not Patterns

A trait declaration specifies a callable type contract. Bind or ignore the whole parameter there, then destructure inside each body that performs work.

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 was placed in an interface declaration that only needs to specify the parameter's complete type.
First discriminating check
Bind or ignore the whole parameter in the trait, then destructure it inside each implementation body according to that implementation's work.

A trait method declaration tells implementors and callers the parameter types, but a declaration without a body has no local execution scope that needs destructured bindings. The failing fixture puts (left, right) in that declaration and gets E0642.

Patterns describe how a body receives a value

The tuple type (i32, i32) is part of the public callable contract. The names left and right are implementation-local choices. A pattern decides how one particular body binds or ignores pieces when it begins executing.

The E0642 explanation asks the trait declaration to use one parameter name. An underscore is also suitable when the declaration does not need a documentation name.

This separation lets implementations choose their own descriptive bindings without changing type compatibility. One implementation may call coordinates x and y; another may call them width and height if the trait's tuple semantics actually permit that interpretation.

The repair moves destructuring to the body

The repaired fixture declares pair: (i32, i32) in the trait. The Sum implementation destructures the tuple in its method parameter and adds the parts.

I could instead write fn translate(pair: (i32, i32)) { let (left, right) = pair; ... }. A body-level let is often clearer for complex nested patterns and gives space to validate the whole value first.

Default trait methods do have bodies, so patterns may be possible according to ordinary function parameter rules. Still, simple parameter names make documentation and generated signatures easier to read.

Parameter names are not part of trait method type identity

Implementations do not need to repeat declaration names. Types, receiver form, generics, lifetimes, and qualifiers such as unsafe or async where supported must remain compatible. Renaming a parameter does not alter dispatch.

Rust's trait item reference defines the interface role. I use names in the trait to communicate meaning to readers and documentation, even though rustc does not match them.

If the two values have different semantics, a named struct may be better than a tuple. Offset { x, y } lets every implementation and call site preserve roles. Tuples work well when position is a natural and stable contract.

Refutable patterns do not belong in ordinary parameters

Function parameters must be matched on every call, so their patterns need to be irrefutable. A tuple destructuring pattern is irrefutable for a tuple of that shape. An enum variant such as Some(value) can fail and therefore needs let else, if let, or match inside the body.

This matters when moving a pattern to fix E0642. I do not blindly paste any pattern into the implementation's parameter list. I ask how failure should be handled and make that branch visible.

Reference patterns can also affect ownership and ergonomics. Taking a tuple by value allows moving its elements; taking &(T, U) borrows them. I let the trait's semantic ownership decide the type, then destructure accordingly.

API evolution starts from the whole contract

Adding another tuple element changes the parameter type and breaks implementations. A non-exhaustive or private-field struct can offer different evolution properties, but each choice affects construction and matching.

I avoid treating parameter patterns as a way to publish a stable data schema. The type declaration owns that schema. Documentation should explain positional meaning and units, while implementations own local binding style.

Compile tests with at least two implementations help reveal which details are truly shared. Behavioural tests should exercise the trait contract rather than depending on one implementation's binding names.

My E0642 checklist

  • Is the pattern inside a trait function declaration with no body?
  • What is the complete parameter type callers and implementors share?
  • Can the declaration bind one descriptive name or use _?
  • Should destructuring occur in the implementation parameter or a body-level let?
  • Is the pattern irrefutable for every admitted input?
  • Would a named struct express tuple positions more safely?
  • Does ownership of the parameter match move or borrow behaviour?
  • Are local binding names being mistaken for public type identity?

The core principle is that an abstract signature describes values crossing a boundary, while patterns describe local handling inside executable code. E0642 keeps those roles separate. I make the trait contract clear first and let each body destructure according to its work.