Mehdi Akiki
Rust Failure Atlas / Runtime, memory, and library APIs

RFA-645 · Case file with fixtures · Case 617 of 694 · Compiler evidence

A Precise Capture Name Must Be Declared in the Current Generics

Precise captures are resolved against the item's declared generics. Declare the real dependency, fix a stale name, or remove capture that the hidden return type does not need.

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
A rename, refactor, nested scope, or generator left a capture identity disconnected from the item's actual generic declaration.
First discriminating check
Determine whether the hidden type really needs T, then declare the dependency, correct the name, or remove the stale capture.

A use<T> bound on an opaque return can refer only to a generic parameter visible on that item. If no T is declared, there is no type identity to capture. The failing fixture contains that stale name and receives E0800.

Scope comes before capture semantics

The compiler first resolves T in the generic parameter scope. It cannot infer that the author meant a type in another module or a parameter removed during refactoring. The official E0800 explanation recommends checking spelling or adding the missing declaration.

The Reference defines generic parameter scopes. I inspect the exact function, impl, or trait item containing the opaque return rather than assuming outer names are available everywhere.

The repaired fixture declares T. In actual code, declaration is correct only when the hidden type or intended API truly depends on it.

Remove stale captures instead of inventing parameters

Refactoring can simplify an implementation so its hidden type no longer mentions T, while leaving use<T> behind. Adding an unused generic merely to satisfy the bound makes callers choose or infer a meaningless type and expands public complexity.

I sketch the hidden concrete type and identify every generic stored in it. If T is absent and future evolution does not need the permission, I remove it. If the implementation does store a T-dependent value, the declaration and capture both belong.

A typo such as use<U> when only T exists deserves a direct rename. Compiler suggestions provide a start, but similarly named policy and payload parameters can carry very different semantics.

Nested items have their own generic scopes

A function declared inside a generic function is a separate item and does not automatically capture outer generic parameters like a closure does. It must declare its own generics or receive concrete values through parameters.

Closures and async blocks capture from their environment operationally, but precise use<...> bounds apply to particular opaque type positions under their own rules. I distinguish lexical value capture from generic identity capture.

Impl-level parameters can be available to associated methods, while method-level parameters extend only that method. Trait Self has special availability. The source declaration remains the authority.

Captures affect auto traits and lifetimes

Permitting a type parameter can influence whether the opaque type is Send, Sync, or bounded by a lifetime when the concrete implementation actually stores related values. The capture list is not documentation-only syntax.

I test important uses from the caller side: moving a return into a thread or task, retaining it beyond an input borrow, and substituting types with different auto traits. These tests reveal over- or under-constrained APIs more clearly than reading the body alone.

The Reference’s precise capturing section should stay beside such tests because rules depend on opaque-return context and edition.

Generated signatures need one source of generics

Macros often generate the generic declaration and capture list in separate template branches. Removing a field can update one but not the other, producing E0800 only for a particular schema.

I parse or represent generics once, then derive both output fragments. Compile tests cover zero parameters, lifetimes only, type and const mixtures, renaming, nested impls, and cfg-selected fields.

Public API generation also needs deterministic ordering. A stale capture should fail before publishing generated clients rather than appear as a user-facing compiler error in a downstream crate.

My E0800 checklist

  • Which name in the capture list is unresolved in the current generic scope?
  • Is it misspelled, removed by refactoring, or declared only on another item?
  • Does the hidden concrete return type actually depend on that parameter?
  • Should the name be removed instead of adding meaningless generic surface?
  • Is an outer item parameter incorrectly assumed to enter a nested item?
  • What lifetime and auto-trait behaviour does real capture create?
  • Do caller-side compile tests exercise short borrows and Send boundaries?
  • Does a generator derive declarations and capture lists from one model?

The core principle is that capture permission can mention only generic identities which this item owns or sees. E0800 catches a missing identity. I repair the source declaration or delete the stale promise according to the hidden type’s actual dependency.