Skip to content

Oddity with lifetime elision and type aliases #140611

Description

@traviscross

In reviewing a test case for,

I was led to the following oddity:

pub struct W<'a>(&'a ());
pub type Alias<'a> = W<'a>;

impl<'a> Alias<'a> {
    fn f1<'x>(self: &W<'a>, x: &'x ()) -> &() { x } //~ `'_ == 'x`, what?
    fn f2<'x>(self: &Alias<'a>, x: &'x ()) -> &() { x } //~ `'_ == 'x`, what?
    fn f3<'x>(&self, _: &'x ()) -> &() { self.0 } //~ OK.
}

impl<'a> W<'a> {
    fn f4<'x>(self: &W<'a>, _: &'x ()) -> &() { self.0 } //~ OK.
    fn f5<'x>(self: &Alias<'a>, x: &'x ()) -> &() { x } //~ `'_ == 'x`, what?
    fn f6<'x>(&self, _: &'x ()) -> &() { self.0 } //~ OK.
}

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

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    A-lifetimesArea: Lifetimes / regions
    C-discussionCategory: Discussion or questions that doesn't represent real issues.
    I-lang-nominatedNominated for discussion during a lang team meeting.
    T-langRelevant to the language team
    on May 3, 2025
  2. removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on May 3, 2025
  3. Veykril commented on May 3, 2025

    @Veykril
    Member

    (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

  4. traviscross commented on May 6, 2025

    @traviscross
    ContributorAuthor

    Interestingly, rust-analyzer, when configured to show inlay lifetime hints, gets f1-f3 and f5-f6 correct (i.e. matches the behavior of rustc) but gets f4 wrong.

    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.
    }
  5. Veykril commented on May 7, 2025

    @Veykril
    Member

    Those inlay hints are entirely syntactically based and known to be wrong in some scenarios (r-a has not implemented semantic lifetime elision yet)

  6. joshtriplett commented on May 7, 2025

    @joshtriplett
    Member

    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.

  7. added
    C-bugCategory: This is a bug.
    and removed
    C-discussionCategory: Discussion or questions that doesn't represent real issues.
    on May 7, 2025
  8. removed
    A-lifetimesArea: Lifetimes / regions
    T-langRelevant to the language team
    on May 7, 2025
  9. 15 remaining items

  10. lcnr commented on May 20, 2025

    @lcnr
    Contributor

    I agree with errs that fixing f2 seems 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 Self in there, then we could keep the existing behavior with a FCW. I separately think that we may want to lint on self: X where X does not contain Self. I don't know the impact of this change so we'd probably need to crater run to get an estimate first

  11. lcnr commented on May 20, 2025

    @lcnr
    Contributor

    Generally, 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.

  12. added a commit that references this issue on May 20, 2025
  13. added a commit that references this issue on May 21, 2025
  14. TKanX commented on Feb 21, 2026

    @TKanX
    Contributor

    I'd like to give it a try, can't guarantee.

    @rustbot claim

  15. TKanX commented on Feb 22, 2026

    @TKanX
    Contributor

    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-level Res values. That cannot reliably distinguish aliases, chains, or cross-crate aliases.
    • Heuristic patches (add TyAlias to 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 Self soundly (no type queries needed). Self is exact and preserves generics. It covers every correct case without extra heuristics.

    Proposal

    1. Add a future-compat lint: when elision uses the impl_self name-match hack (not Self), emit a warning and a rustfix suggestion (self: &Struct → self: &Self).
    2. Run crater to measure actual breakage.
    3. Hold a warning period and iterate suggestions if needed.
    4. Remove the impl_self name-match hack after the warning period.

    Next steps

    1. Add a future_incompatible lint: when self-elision triggers via impl_self name matching rather than via Self, emit a warning with a rustfix suggestion (self: &Struct → self: &Self).
    2. Crater run.
    3. Warning period.
    4. Remove the impl_self hack.
  16. lcnr commented on Mar 2, 2026

    @lcnr
    Contributor

    @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!() }
    }
  17. TKanX commented on Mar 2, 2026

    @TKanX
    Contributor

    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> in impl W): FCW, eventually error
    • f1, f2, f5: FCW, eventually error

    all with suggestion to use &self or self: &Self.

    as @compiler-errors already noted: the name-match hack is just fundamentally unprincipled. it shallow-compares Res values, 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_ty already handles &self / self: &Self soundly 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: &ConcreteType exists in practice: my guess very little since people mostly just write &self.

  18. theemathas commented on Mar 2, 2026

    @theemathas
    Contributor

    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.)

  19. lcnr commented on Mar 4, 2026

    @lcnr
    Contributor

    crater first to see how much self: &ConcreteType exists in practice: my guess very little since people mostly just write &self.

    could try that 👍 want to open a PR?

  20. TKanX commented on Mar 26, 2026

    @TKanX
    Contributor

    Crater run: #153692 (comment)

  21. removed
    I-types-nominatedNominated for discussion during a types team meeting.
    on Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-lifetimesArea: Lifetimes / regionsC-bugCategory: This is a bug.I-lang-radarItems that are on lang's radar and will need eventual work or consideration.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamneeds-craterThis change needs a crater run to check for possible breakage in the ecosystem.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions