RFA-554 · Case file with fixtures · Case 526 of 694 · Compiler evidence
The Data Inside a Rust Reference Must Outlive the Reference
A reference cannot promise access longer than borrowed data nested inside its referent. State the required inner-outlives-outer relation explicitly.
- 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 nested borrowed relationships of the referent are unconstrained and may expire before the reference promising access to that referent.
- First discriminating check
- Identify outer and nested roles, require the inner lifetime to outlive the outer, or shorten/redesign the returned projection.
A reference can be short even when the object it points to contains long-lived borrows. The reverse is unsafe: a long reference cannot provide access to an object whose internal borrowed data may expire sooner. E0491 detects that missing relationship.
The failing fixture selects &'outer Handler<'inner> as an associated type. Nothing says 'inner lasts at least as long as 'outer.
Validity includes the referent's nested references
Handler<'inner> contains a function pointer accepting &'inner str. More generally, a lifetime parameter can describe references stored anywhere in the referent's type.
For &'outer Handler<'inner> to remain valid throughout 'outer, the Handler value and the borrowed relationships encoded by its type must be usable throughout that period. An unconstrained shorter 'inner cannot support this promise.
The official E0491 page uses this nested-reference shape and requires the inner lifetime to outlive the outer one.
Read the colon as outlives
The repaired fixture adds 'inner: 'outer. I read this aloud as “inner outlives outer.”
It does not mean the two lifetimes are equal, nor that inner begins first in wall-clock terms. It is a validity relationship: anything valid for 'inner remains valid for the whole 'outer requirement.
The Reference's lifetime bounds section defines these constraints. Reading them in the correct direction prevents many trial-and-error edits.
Type well-formedness is checked for every admitted substitution
The fixture does not construct the problematic associated type in main, yet the definition fails. The implementation promises that its Output is valid for every 'outer and 'inner admitted by the impl header.
Without a bound, that includes combinations where inner is shorter. Rust checks generic contracts before monomorphisation instead of waiting for a particular caller to trigger undefined access.
This is why adding one friendly example value cannot prove the definition safe. The impl domain itself must exclude invalid lifetime combinations.
Shortening outer is another conceptual option
Sometimes the desired API does not need such a long outer reference. A method can return a borrow tied to the shorter of relevant inputs. Lifetime inference often finds that intersection naturally when signatures relate the inputs.
At an associated-type declaration, however, the trait contract fixes the form. I either state the necessary outlives bound, change the trait to express a more flexible relationship, or use a generic associated type when output varies per borrow.
I avoid strengthening everything to 'static. A local 'inner: 'outer relation admits far more useful borrowed values.
Variance influences which lifetime substitutions are allowed
Reference and function-parameter positions can have different variance behaviour. Complex nested types may therefore produce bounds that feel counterintuitive.
The Reference's subtyping and variance chapter is useful when a simple “references get shorter” model stops predicting results. I reduce the type to one field and add layers back rather than guessing several bounds at once.
Naming lifetimes by role helps reviews
'a and 'b are compact, but 'outer and 'inner make direction visible in a teaching fixture. In production I use role names such as 'store, 'view, or 'request when several relationships matter.
The names do not affect checking. They help humans see whether a proposed bound matches actual ownership, especially in associated types far from construction sites.
Tests should include deliberately different lifetimes
If every test uses string literals or other 'static data, a wrong relationship can look correct because static satisfies nearly every shorter bound. I include one owned local value and create the projected reference only inside a nested scope. Compile-pass and compile-fail fixtures then exercise the direction of the outlives relation. This is more convincing than a test where both lifetimes accidentally collapse to the same scope, and it guards against later “simplification” that removes the bound without understanding why it exists.
My E0491 checklist
- What does the outer reference promise to keep accessible?
- Which borrowed data is nested inside the referent type?
- Can that inner data expire before the outer reference?
- Is the needed relation
'inner: 'outer? - Could the returned outer borrow be shorter instead?
- Does the trait need a generic associated type for per-call output?
- Is variance affecting the direction I expected?
- Have I avoided replacing a local relation with unnecessary
'static?
The core principle is that a reference's validity includes the validity of borrowed data inside its target. I express the minimum outlives relationship that makes the whole nested type honest.