Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-591 · Case file with fixtures · Case 563 of 694 · Compiler evidence

Rust Closure Parameter Types Must Match the Caller

Explicit closure annotations constrain the generated callable type. Start from the callback's required input and annotate only when it clarifies a real boundary.

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
An explicit closure annotation contradicts the parameter type in the caller-owned callback protocol despite matching its arity.
First discriminating check
Read the exact callback bound and iterator item type, then remove the annotation for inference or translate the supplied type deliberately.

A closure parameter annotation is not documentation that Rust may ignore. It becomes part of the anonymous closure type's callable signature. The failing fixture declares |value: &str|, while visit requires a closure callable with i32. Rust reports E0631.

The caller owns the input protocol

The bound F: Fn(i32) tells me that visit may supply an integer. A compatible callback must accept exactly that input type, subject to normal lifetime and coercion rules. Its body printing values does not make all printable types interchangeable.

The E0631 explanation recommends changing or removing the conflicting annotation. Removing it lets context infer i32. Keeping : i32 can be useful while documenting or debugging the boundary.

I inspect the API definition rather than guessing from the callback body. Iterator and framework callbacks frequently pass references, tuples, or wrapper types that look similar to the data I ultimately need.

The repair aligns both sides

The repaired fixture changes the parameter to i32 and asserts the delivered value. This differs from E0593, where the number of parameters was wrong. Here arity matches but the one parameter's type does not.

If the closure genuinely needs text, I can convert the integer inside it or change the API to supply text when that is the correct contract. Changing the generic bound just to accept one callback can push an unnecessary allocation or formatting policy into every caller.

The closure reference separates call parameters from captured fields. I avoid capturing a string version from outside to bypass the supplied integer, because that can use stale or unrelated data.

References create common near-misses

Iterator filter receives a reference to each item because it must decide whether to keep the original. A map closure usually receives the iterator item itself. If the item is already a reference, callback types may contain another layer.

I write the iterator's Item type and the adapter's callback bound. Patterns such as |&value| can destructure a reference for Copy items. For non-Copy items, moving through a shared reference is not allowed, and borrowing is the better contract.

Lifetime errors can follow after the basic type matches. A callback accepting one particular borrowed lifetime is different from a higher-ranked callback that must work for any input lifetime. I solve type shape first, then review lifetime generality.

Inference is useful but boundaries still deserve names

Removing annotations often fixes E0631, and inference reduces noise. At complex public boundaries, a named helper function can be clearer than a heavily annotated closure. Its signature makes input and output types searchable and independently testable.

Compiler diagnostics may show a long generated type. I introduce intermediate bindings with explicit types or use a small adapter closure that translates the framework input into a domain function. This keeps framework-specific wrappers at the edge.

For example, a web framework may supply State<App> while domain code needs &Repository. The adapter unwraps state and calls a typed function. I do not change domain signatures to mirror every framework extractor.

Fn family and parameter type are separate axes

After fixing E0631, a capture may make the closure FnMut or FnOnce while the API requires Fn. That is a separate ownership decision. The standard Fn documentation describes repeatable calls through shared access.

I check how often the API calls the closure, whether it may retain it, and on which thread. Parameter compatibility alone does not establish Send, 'static, or capture safety.

My E0631 checklist

  • What exact input type does the callback bound say the caller supplies?
  • Does arity match while one parameter annotation conflicts?
  • Can context infer the type more accurately than a copied annotation?
  • Is an adapter needed between framework and domain types?
  • Does the iterator pass an item, a reference, or one tuple item?
  • Is captured state bypassing the intended callback input?
  • After type repair, what Fn, lifetime, Send, and retention guarantees remain?
  • Would a named typed helper make the boundary easier to test?

The core principle is that a callback does not choose what its caller sends. E0631 exposes disagreement at that protocol boundary. I start from the required signature, translate deliberately, and let inference remove annotations that add confidence without adding truth.