RFA-557 · Case file with fixtures · Case 529 of 694 · Compiler evidence
Nested Rust Generic Scopes Cannot Redeclare the Same Lifetime Name
Lifetime names identify relationships within generic scopes. Distinct stored and call-local borrows need distinct names so bounds cannot silently refer to the wrong one.
- 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
- Two independently owned generic scopes reuse one visible lifetime identifier and make later relationships unable to refer unambiguously to the outer parameter.
- First discriminating check
- Classify the inner relation as independent, identical, or elidable, then rename it, reuse the outer parameter, or rely on correct elision.
Lifetime names are not comments attached to references. They are generic parameters with scope and identity. Redeclaring 'a inside an impl that already has 'a would hide which relationship each use means, so Rust reports E0496.
The failing fixture has impl<'a> View<'a> and then fn compare<'a>. The method attempts to introduce a second lifetime with the same visible name.
The two lifetimes have different owners
The impl lifetime describes the borrowed text stored in each View. The method lifetime describes one other string passed to a particular call. Those relationships can be completely independent.
Reusing the spelling would not make them equal. It would shadow the outer name, just as an inner variable may shadow an outer variable, but Rust forbids this for lifetime parameters in this context because bounds would become dangerously hard to read.
The official E0496 page recommends changing one lifetime name.
Role names reveal the API
The repaired fixture uses 'stored for the view and 'input for the method argument. The method only compares text, so there is no outlives requirement between them.
This naming is longer than 'a and 'b, but it makes independence obvious. In a small conventional signature, short names remain fine. I use role names once three or more lifetimes or nested scopes appear.
The Reference defines generic parameter scopes, including how parameters are visible in item declarations and bodies.
Do not remove the method lifetime without checking elision
In this example, other: &str could use an elided method-local lifetime because the returned value is only bool. Removing the explicit parameter is a clean alternative.
If the method returns other or relates it to self, elision may select a different output relationship than intended or become ambiguous. I expand the desired signature first, then decide which names can safely disappear.
Fewer lifetime annotations are good only when the remaining contract is still correct.
Equal lifetimes should reuse the outer parameter, not redeclare it
If other genuinely must live for the same 'stored lifetime, the method can write other: &'stored str without introducing a new generic parameter. This makes calls more restrictive and may be unnecessary for immediate comparison.
I distinguish:
- reuse: refer to the existing lifetime because the relationship is the same;
- rename: declare a separate lifetime because callers choose it independently;
- elide: let rules introduce an unimportant local lifetime.
E0496 tells me redeclaration is not a valid fourth option.
Higher-ranked binders create another nested scope
Bounds such as for<'a> Fn(&'a str) introduce lifetime names inside the binder. Macro-generated predicates can accidentally collide with surrounding names.
The Reference lists lifetime parameters among generic parameters. I inspect binder boundaries when an error points into a long where clause rather than a simple method header.
Renaming is semantic documentation
The compiler treats 'input and 'b equivalently, but reviewers do not. Meaningful names help verify outlives direction and prevent a future constraint from accidentally tying a call-local borrow to stored state.
I keep names consistent across a trait and its implementation when they represent the same roles, even though spelling does not affect signature compatibility.
Refactors should preserve relationships, not letters
When moving a method from an impl into a trait, copying 'a mechanically can create a collision or accidentally connect previously independent borrows. I write the lifetime relationships in plain language before moving the signature: the view borrows stored text, the call borrows an input, and the result borrows whichever source it returns. Then I rebuild the generic scopes. This approach also makes code review easier because renamed lifetimes are judged by their role rather than by whether every old letter survived unchanged.
My E0496 checklist
- Which outer generic scope already declares this lifetime name?
- What data relationship does the outer lifetime represent?
- Is the inner lifetime independent, identical, or safely elidable?
- Can I use role names such as
'storedand'input? - If they are identical, should the method reference the existing parameter?
- Does output elision create the relationship I intend?
- Is a
for<'a>binder or macro creating the collision? - Do trait and impl names remain readable even when spelling is not semantic?
The core principle is that lifetime parameters have lexical identity. I give separate relationships separate names and reuse an existing parameter only when the API truly requires the same lifetime.