RFA-567 · Case file with fixtures · Case 539 of 694 · Compiler evidence
A Rust Function Pointer Cannot Return a Reference for Any Unconstrained Lifetime
Borrowed outputs need a source relationship. Tie the output to an input lifetime, return static data, or return ownership instead of promising arbitrary references from nowhere.
- 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 higher-ranked signature promises an arbitrary borrowed output without any lifetime-bearing provenance in its input tuple.
- First discriminating check
- Tie output to an input lifetime, return genuinely static data, or return owned/shared storage whose lifetime the caller controls.
for<'a> fn() -> &'a str sounds flexible, but it promises something impossible for ordinary borrowed data: the caller may choose any 'a, and the function must return a reference valid for that choice without receiving a source carrying it. Rust reports E0581.
The failing fixture places the quantified lifetime only in the function pointer's return type.
A borrowed output needs provenance
References do not create storage. A function returning one must point into an input, static allocation, leaked allocation, or state reachable through another valid owner.
When 'a appears in an input and output, the signature can say the result borrows from that input. With no input occurrence, the higher-ranked caller choice has no constraint.
The official E0581 explanation describes exactly this missing connection.
For every lifetime is an extremely strong promise
The binder for<'a> means the pointer works for every appropriate 'a. It does not mean the implementation picks a convenient lifetime internally.
If the caller chooses a very long lifetime, a function returning a reference into a local temporary cannot satisfy it. No implementation can generally manufacture the promised borrow.
I read higher-ranked bounds aloud as “for every lifetime” before deciding whether the API is possible.
Static is correct for genuinely permanent data
The repaired fixture defines fn() -> &'static str and returns a string literal. The source data is valid for the entire program, so the signature is honest.
'static is not a generic workaround for borrowed configuration or request data. It fits this provider because the returned name is compiled into the binary.
The Reference defines function pointer types and their higher-ranked parameter possibilities.
Tie output to input for borrowed transformation
An identity-like function can use:
for<'a> fn(&'a str) -> &'a str
Now each caller chooses 'a by lending an input, and the output cannot outlive that input. Parsers that return slices and selectors that return fields often use this relationship.
The lifetime elision rules can omit the spelling in simple named functions, but explicit pointer types make the relationship useful to inspect.
Return ownership when data is produced
A provider generating new text should return String, Box<str>, or another owner. This frees the result from an input borrow and lets the caller choose its storage lifetime by owning it.
Returning ownership can allocate, but attempting to express generated data as an arbitrary borrowed reference is not an optimisation. It is a missing owner.
Shared cached data may use Arc<str> when several consumers need independent handles and static storage is inappropriate.
A closure object may borrow from its environment
A closure can capture a reference and return it under constraints tied to the closure's own lifetime. Erasing it behind dyn Fn() -> &str involves object lifetime and output rules different from a bare higher-ranked function pointer.
I model who owns the environment and how long the callable is stored. Moving from a function pointer to a closure does not allow returning references beyond captured storage.
Provider registries should make storage ownership visible
A registry of callbacks returning text needs one shared output strategy. Static labels fit a plain function pointer; generated values fit owned String; interned or cached values may fit a handle tied to the registry owner. I avoid hiding these different lifecycles behind one overly clever higher-ranked alias. A compile test with a local captured string and a separate test with a literal reveal which providers the interface really admits. That evidence is more useful than a signature that looks general but no function can implement.
My E0581 checklist
- Where does the returned reference's storage come from?
- Does the quantified lifetime appear in any input type?
- Am I promising that the caller may choose any lifetime?
- Is the data genuinely static?
- Should output borrow from an input through the same lifetime?
- Should newly produced data be owned instead?
- Does a captured environment have a clearly bounded owner?
- Can a small implementation actually satisfy the written pointer type?
The core principle is that every borrowed output needs a validity source. I connect it to an input, promise static only for permanent data, or return ownership.