RFA-568 · Case file with fixtures · Case 540 of 694 · Compiler evidence
A Higher-Ranked Rust Output Lifetime Must Be Tied to an Input
Fn notation includes an associated output type. A quantified borrow returned there needs the same lifetime represented in an input, or a concrete static/owned result.
- 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
- Fn arrow syntax hides an associated output projection whose borrowed lifetime lacks a real per-call input source.
- First discriminating check
- Put the lifetime on a reference input that owns the returned view, or choose an explicit static or owned output contract.
Function trait syntax hides an associated type: Fn(Args) -> Output. If a higher-ranked lifetime appears only inside that output, there is no input value from which the returned borrow can be derived. Rust reports E0582.
The failing fixture requires F: for<'a> Fn(u32) -> Option<&'a str>.
The caller would choose a lifetime from nowhere
for<'a> says the callback works for every caller-chosen 'a. Its only input is an owned u32, which carries no borrow with that lifetime. Yet the output promises a string reference valid for 'a.
No ordinary callback can satisfy this for arbitrary lifetimes unless the result is effectively static, and that should be stated directly rather than through an unconstrained binder.
The official E0582 page explains that the lifetime must appear in trait input types, not only an associated-type binding.
The repaired callback returns a view of its input
The repaired fixture uses for<'a> Fn(&'a str) -> Option<&'a str>. The caller supplies storage through the borrowed input, and the returned reference is bounded by that same borrow.
The identity callback proves the contract is implementable. A parser could return a substring; a validator could return the original input only when valid.
Fn output is an associated type
The Fn trait documentation shows its Output associated type through the parent call traits. Arrow syntax is ergonomic sugar around that callable contract.
Thinking of output as a projected associated type helps explain the diagnostic wording. Rust must ensure a lifetime used in the projection is constrained by inputs to the trait reference.
A generic associated input might not use its lifetime
E0582 can also appear when the input is X::Assoc<'a>. An implementation of that generic associated type may ignore 'a, so its mere textual presence does not always prove a real lifetime relationship.
An additional &'a () witness input can constrain the lifetime, but adding dummy parameters can make an API awkward. I first ask whether a direct reference input or different trait shape expresses the provenance better.
The official error page covers this less obvious GAT case.
Static and owned outputs are alternative contracts
If every callback returns global strings, use Fn(u32) -> Option<&'static str>. If it computes new strings, return Option<String> or a shared owner.
These forms answer where data lives. Higher-ranked syntax should not be used to make the lifetime look flexible when ownership is unresolved.
HRTBs model repeatable borrowing capability
The Reference defines higher-ranked trait bounds. They are especially useful when one callback will process many short-lived inputs over time.
Each call gets its own 'a; the callback cannot retain one borrow and pretend it works for the next. This property is valuable in visitors and streaming parsers.
Test with local inputs, not only literals
String literals are static and can accidentally satisfy overly strong return requirements. I create an owned String in a nested scope, pass a borrow, and prove the output cannot escape it.
Compile-fail tests complement runtime assertions for these relationships because invalid escaping code should never run.
API aliases should spell the borrowing direction once
If the callback shape is reused, I create a named trait or alias with documentation such as “returns a view into the supplied input.” Call sites then do not repeat a dense HRTB and accidentally change only its output. I still keep one minimal compiler fixture for the expanded bound. When an associated input type is involved, the documentation states whether that type is guaranteed to carry the lifetime; otherwise a future implementation may legally ignore it and revive E0582 in an apparently unrelated generic container.
My E0582 checklist
- Where does the lifetime occur in the
Fninput tuple? - Is it present only in
Output? - Does an associated input type actually have to use that lifetime?
- Can output borrow directly from a reference input?
- Is the true result static or owned instead?
- Does a dummy lifetime witness clarify or distort the API?
- Will the callback process many independently borrowed inputs?
- Do tests use non-static local data to exercise the contract?
The core principle is that higher-ranked output borrows need provenance in each call. I put the lifetime on a real input or choose a concrete storage contract.