Repository navigation
Oddity with lifetime elision and type aliases #140611
Description
Activity
- addedneeds-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triagingThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triagingA-lifetimesArea: Lifetimes / regionsArea: Lifetimes / regionsC-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.I-lang-nominatedNominated for discussion during a lang team meeting.Nominated for discussion during a lang team meeting.T-langRelevant to the language teamRelevant to the language team
on May 3, 2025 - removedneeds-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triagingThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
on May 3, 2025 (The FLS, as best I can tell on a skim, does not document lifetime elision at all.)
It does, but it also does not capture this oddity I think: https://github.com/rust-lang/fls/blob/main/src/types-and-traits.rst#lifetime-elision
Interestingly, rust-analyzer, when configured to show inlay lifetime hints, gets
f1-f3andf5-f6correct (i.e. matches the behavior ofrustc) but getsf4wrong.That is, it annotates it like this:
impl<'a> Alias<'a> { fn f1<'x>(self: &W<'a>, x: &'x ()) -> &'x () { x } //~ `'_ == 'x`, what? fn f2<'x>(self: &Alias<'a>, x: &'x ()) -> &'x () { x } //~ `'_ == 'x`, what? fn f3<'0, 'x>(&self, _: &'x ()) -> &'0 () { self.0 } //~ OK. } impl<'a> W<'a> { fn f4<'x>(self: &W<'a>, _: &'x ()) -> &'x () { self.0 } //~ Wrong r-a annotation. fn f5<'x>(self: &Alias<'a>, x: &'x ()) -> &'x () { x } //~ `'_ == 'x`, what? fn f6<'0, 'x>(&self, _: &'x ()) -> &'0 () { self.0 } //~ OK. }
Those inlay hints are entirely syntactically based and known to be wrong in some scenarios (r-a has not implemented semantic lifetime elision yet)
We discussed this in today's @rust-lang/lang meeting. This seems emphatically like a bug. We should try to fix it, and do a crater run to see if fixing it breaks anything.
- addedC-bugCategory: This is a bug.Category: This is a bug.and removedC-discussionCategory: Discussion or questions that doesn't represent real issues.Category: Discussion or questions that doesn't represent real issues.
on May 7, 2025 - addedI-compiler-nominatedNominated for discussion during a compiler team meeting.Nominated for discussion during a compiler team meeting.
on May 7, 2025 - removedA-lifetimesArea: Lifetimes / regionsArea: Lifetimes / regionsT-langRelevant to the language teamRelevant to the language team
on May 7, 2025 15 remaining items
I agree with errs that fixing
f2seems quite doable. I also think that resolving aliases to their underlying type is difficult at the point where we handle lifetime elision.I believe that we can future compat lint changes to our lifetime elision rules. So if we decide to only do the self-type lifetime elision if there's an actual
Selfin there, then we could keep the existing behavior with a FCW. I separately think that we may want to lint onself: XwhereXdoes not containSelf. I don't know the impact of this change so we'd probably need to crater run to get an estimate firstGenerally, I think lifetime elisions is one of the areas where there are a lot of not-nice edge-cases right now and no awesome solution is possible. I personally don't think we should spend too much of our time on it and should prioritize other things as a project. Though ofc individual contributor is more than welcome to dive into this regardless. I would still love this area to be cleaned up.
Reacted by 许杰友 Jieyou Xu (Joe)- added a commit that references this issue
on May 21, 2025 I'd like to give it a try, can't guarantee.
@rustbot claim
cc @lcnr @compiler-errors @traviscross
We should stop extending the name-level hack and adopt
Self-only elision via an FCW.Rationale
- The resolver (
rustc_resolve) runs before type information exists. It compares name-levelResvalues. That cannot reliably distinguish aliases, chains, or cross-crate aliases. - Heuristic patches (add
TyAliasto the filter; record alias RHS locally) improve coverage but cannot reach correctness at this stage. They still miss cross-crate aliases, alias chains, forward refs, and cases where generic args differ. - The code already handles
Selfsoundly (no type queries needed).Selfis exact and preserves generics. It covers every correct case without extra heuristics.
Proposal
- Add a future-compat lint: when elision uses the
impl_selfname-match hack (notSelf), emit a warning and a rustfix suggestion (self: &Struct→self: &Self). - Run crater to measure actual breakage.
- Hold a warning period and iterate suggestions if needed.
- Remove the
impl_selfname-match hack after the warning period.
Next steps
- Add a
future_incompatiblelint: when self-elision triggers viaimpl_selfname matching rather than viaSelf, emit a warning with a rustfix suggestion (self: &Struct→self: &Self). - Crater run.
- Warning period.
- Remove the
impl_selfhack.
- The resolver (
@TKanX looking at the original example, what would be your desired behavior here, for each of the variants, what will be the elided lifetime of the return type/where do we error?
pub struct W<'a>(&'a ()); pub type Alias<'a> = W<'a>; impl<'a> Alias<'a> { fn f1<'x>(self: &W<'a>, x: &'x ()) -> &() { todo!() } fn f2<'x>(self: &Alias<'a>, x: &'x ()) -> &() { todo!() } fn f3<'x>(&self, _: &'x ()) -> &() { todo!() } } impl<'a> W<'a> { fn f4<'x>(self: &W<'a>, _: &'x ()) -> &() { todo!() } fn f5<'x>(self: &Alias<'a>, x: &'x ()) -> &() { x } fn f6<'x>(&self, _: &'x ()) -> &() { todo!() } }
To be fair my previous comment was mostly summarizing @compiler-errors and the discussion here into a concrete plan, not adding much new.
- f3, f6 (
&self): self lt, no change - f4 (
self: &W<'a>inimpl W): FCW, eventually error - f1, f2, f5: FCW, eventually error
all with suggestion to use
&selforself: &Self.as @compiler-errors already noted: the name-match hack is just fundamentally unprincipled. it shallow-compares
Resvalues, can't see through aliases, and silently picks wrong lifetimes when it whiffs (f1/f2/f5). f4 "working" isn't the hack being correct, it's the hack not whiffing. a heuristic that silently gives you wrong lifetimes in some cases is worse than just erroring.is_self_tyalready handles&self/self: &Selfsoundly without any name matching. so the clean end state is: self-elision only via those two forms, FCW everything else, remove the hack.crater first to see how much
self: &ConcreteTypeexists in practice: my guess very little since people mostly just write&self.- f3, f6 (
A quick github search to find code with
self: &ConcreteType: https://github.com/search?q=language%3Arust+%2F%28%3F-i%29%5C%28self%3A+%26%5B%5ES%27%5D%2F&type=code(Has many false positives, and probably many false negatives.)
crater first to see how much
self: &ConcreteTypeexists in practice: my guess very little since people mostly just write&self.could try that 👍 want to open a PR?
Reacted by Tony KanReacted by Tony KanCrater run: #153692 (comment)
- removedI-types-nominatedNominated for discussion during a types team meeting.Nominated for discussion during a types team meeting.
on Jun 2, 2026
In reviewing a test case for,
mismatched-lifetime-syntaxeslint #138677I was led to the following oddity:
Playground link
This was noticed long ago in #60944 (comment), which is presumably when the test case was added, but I can't immediately find later discussion.
The Reference doesn't document this behavior.
(The FLS, as best I can tell on a skim, does not document lifetime elision at all.)If there's a good reason for this as the correct behavior, we should probably write this down in the Reference. Otherwise, I'm going to propose we agree to try to do away with this somehow, if possible, maybe over an edition.
@rustbot labels +C-discussion +T-lang +I-lang-nominated +A-lifetimes
cc @rust-lang/lang @ehuss