RFA-644 · Case file with fixtures · Case 616 of 694 · Compiler evidence
Precise Capture Lists Can Name Only Generic Parameters
use<...> controls which lifetime, type, and const parameters an opaque return type captures. It is not a dependency list for functions, locals, modules, or runtime values.
- 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
- The list was treated as runtime dependency metadata instead of a type-level declaration of generic identities retained by a hidden type.
- First discriminating check
- Expand the hidden concrete type and list only its declared lifetime, type, and const parameter dependencies.
An opaque impl Trait return has a hidden concrete type. A use<...> bound controls which generic parameters that hidden type is allowed to capture. It does not list every function or value used while computing the result. The failing fixture places the function item main in the list and receives E0799.
Capture refers to the hidden type
A returned iterator may store a borrow with lifetime 'a, mention a type parameter T, or depend on a const parameter N in its concrete type. These are type-level captures. Calling a helper function in the body does not make that function name part of the return type’s generic identity.
The official E0799 explanation says only type and const parameters, including Self, may occupy the relevant identifier positions; lifetime parameters have their lifetime syntax. The Reference documents precise capturing for return-position opaque types.
I write out the probable hidden type and mark which generics occur inside it. This turns abstract capture syntax into a concrete ownership question.
The repaired list names a declared type parameter
The repaired fixture declares T and permits the opaque result to capture it. Its unit hidden type does not need T, but the fixture demonstrates the accepted category precisely.
In real code I list only parameters the hidden type needs. An empty use<> can state non-capture where allowed. Over-capturing can impose unnecessary lifetime or semver constraints; under-capturing produces a diagnostic because the concrete type uses information the interface denied.
Edition rules affect automatic capture. Precise bounds are most useful where the API needs to promise a narrower relation than defaults or where migration changes inference. I pin edition and MSRV in compile fixtures.
Runtime dependencies belong elsewhere
If the function body reads configuration, calls main_helper, or constructs a value from a local, those dependencies are ordinary code. Captured locals in a closure or async block become part of that generated value, but they are represented through their types and lifetimes, not local variable names in an opaque use bound.
This distinction matters in reviews. use<T> does not grant runtime access to T; it permits the hidden type to depend on that generic identity. It is closer to an interface-level closure over generics than a module import.
I avoid describing the syntax as “imports.” The Reference’s use-bound grammar calls it a bound and defines the permitted generic arguments.
Public opaque returns have evolution constraints
impl Trait hides a concrete type name but still exposes trait bounds, auto traits, lifetimes, and capture relationships. Changing the implementation to store a previously uncaptured borrow can become a breaking or rejected change.
I design the capture list from expected evolution. If future implementations may need request-scoped data, promising no lifetime capture can be too narrow. If a returned handle should be independent and movable into a long-lived task, accidental capture is harmful.
Compile tests instantiate short lifetimes, multiple type parameters, and spawning boundaries where relevant. They prove the public relationship rather than relying on one happy-path body.
Macro output should use resolved generic identity
A macro may receive tokens named T and also generate a local or function named T in another namespace. Capture lists need the correct generic parameter. I generate them from the parsed generic declaration rather than scanning identifiers in the body.
Renaming a generic must update its capture list. Structured generation prevents string-level drift. Diagnostics E0799 and E0800 distinguish a name of the wrong kind from a missing parameter, which helps locate generator mistakes.
I keep a direct official source link in documentation because precise-capture behaviour is relatively new and edition-sensitive. Copied snippets without their toolchain context age quickly.
My E0799 checklist
- Which name in
use<...>resolves to a function, local, module, or other non-parameter item? - What is the hidden concrete return type likely to store?
- Which declared lifetime, type, and const generics occur in that type?
- Is the capture list narrower than edition defaults for an intentional API reason?
- Are runtime dependencies being confused with type-level generic capture?
- Could future implementation changes need a relationship excluded today?
- Does a macro build the list from declared generics rather than body identifiers?
- Do MSRV and edition-pinned compile tests prove the intended boundary?
The core principle is that precise capture describes generic identity retained by a hidden type. E0799 rejects unrelated names. I derive the list from the concrete type’s real lifetime, type, and const dependencies and treat it as a public ownership promise.