Skip to content

In-band lifetimes: Lint against single-use lifetime names #44752

Description

@nikomatsakis

View all comments

Once support for '_ lands in #44691, the next step for #44524 is to implement a lint that warns against "single use" lifetime names.

Current status

The lint is partially implemented but needs to be completed. Here is a checklist:

  • Issue warnings at the right times; this gist provides a comprehensive test case.
    • "Warning: never used at all" falls under unused_lifetimes
  • For each warning, issue a suggested fix:
    • For a single-use lifetime 'a appearing in &'a T, suggest &T
    • For a single-use lifetime 'a appearing in any other place, suggest '_
    • One challenge: the binder must be removed too
      • cc @estebank -- is it possible to give suggested fixes that make changes to multiple spots at once? I guess that might just be multiple suggestions?
  • [] We'll know this is really done when we can enable by default in rustc crates and apply rustfix with suggestions

Older Background

The idea is that an explicit name like 'a should only be used (at least in a function or impl) to link together two things. Otherwise, you should just use '_ to indicate that the lifetime is not linked to anything.

Until #15872 is closed, we should only lint for single-use lifetime names that are bound in functions. Once #15872 is closed, we can also lint against those found in impl headers.

We can detect cases where a lint is valid by modifying the resolve_lifetimes code:

  • This code basically walks over the HIR and resolves all lifetime names.
  • It maintains a stack of scopes indicating what names are valid.
  • The function with() is used to push new scopes on the stack. It is also given a closure which will execute with the new name bindings in scope.
    • with() gets called for impls and other kinds of items from here; for methods and functions in particular it is called from visit_early_late).
  • Once a name is in scope, resolve_lifetime_ref() is called to resolve an actual reference to a named lifetime.
    • This could be used, for example, to update some information in the Scope, e.g. counting how many times a particular lifetime was referenced.
  • Then, before with() returns, we could scan the lifetimes and check for those that were only referenced 1 time (or 0 times...) and issue a lint warning.

(There are some directions for how to add a lint under the header "Issuing future compatibility warnings" in the rustc-bug-fix-procedure page on forge -- we can skip the "future compatibility" parts here.)

Activity

  1. added
    E-mentorCall for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion.
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    on Sep 21, 2017
  2. added
    C-enhancementCategory: An issue proposing an enhancement or a PR with one.
    on Sep 21, 2017
  3. cramertj commented on Sep 22, 2017

    @cramertj
    Member

    we should only lint for single-use lifetime names that are bound in functions

    Also, I believe, only for lifetime names that appear in argument position. Currently we require explicitly binding output-only lifetimes with a name:

    fn bar<'a>() -> &'a u8 { &5 } // OK
    fn bar() -> &'_ u8 { &5 } // ERROR: missing lifetime specifier
  4. nikomatsakis commented on Sep 22, 2017

    @nikomatsakis
    ContributorAuthor

    @cramertj Hmm. I would consider the proper way to declare bar() to probably be fn bar() -> &'static u8, really, though there are subtleties involved in rare cases (e.g., around invariance).

  5. cramertj commented on Sep 22, 2017

    @cramertj
    Member

    @nikomatsakis Yes, 'static is the right thing to use. I wanted to point out that we shouldn't recommend '_ here, as it won't work.

  6. changed the title [-]Lint against single-use lifetime names[/-] [+]In-band lifetimes: Lint against single-use lifetime names[/+] on Sep 25, 2017
  7. gaurikholkar-zz commented on Sep 30, 2017

    @gaurikholkar-zz

    Is this up for grabs?

  8. cramertj commented on Oct 3, 2017

    @cramertj
    Member

    @gaurikholkar Go for it!

  9. gaurikholkar-zz commented on Oct 3, 2017

    @gaurikholkar-zz

    Will start working on it

  10. nikomatsakis commented on Oct 16, 2017

    @nikomatsakis
    ContributorAuthor

    @gaurikholkar hey, just checking in! How's it going? Any blockers?

  11. nikomatsakis commented on Oct 31, 2017

    @nikomatsakis
    ContributorAuthor

    Some examples:

    fn foo<'x>(_: &'x u32) { }

    This should lint against 'x and suggest &u32 instead.

    struct Foo<'a> { x: &'a u32 }
    fn foo<'x>(_: Foo<'x>) { }

    This should lint against 'x and suggest Foo<'_> instead.

    struct Foo<'a, 'b> { f: &'a &'b u32 }
    fn foo<'x, 'y>(foo: Foo<'x, 'y>) -> &'x u32 { foo.f }

    This should lint against 'y and suggest Foo<'x, '_> instead.

    struct Foo<'a, 'b> { f: &'a &'b u32 }
    fn foo<'x, 'y>(foo: Foo<'x, 'y>) -> &'y u32 { foo.f }

    This should lint against 'x and suggest Foo<'_, 'y> instead.

    fn foo<'x>() -> &'x u32 { &22 }

    This should not lint, because 'x appears only in the return type.

    trait Trait<'a> { }
    impl<'a, T> Trait<'a> for T { }
    
    fn foo<'x, T>(t: T)
    where T: Trait<'x>
    { 
    }
    
    fn main() { 
      foo(22);
    }

    This should not lint, because at present '_ does not work in that position (which we should fix).

  12. nikomatsakis commented on Nov 11, 2017

    @nikomatsakis
    ContributorAuthor

    OK, let's start with this specific test:

    fn deref<'x>(v: &'x u32) -> u32 {
        *v
    }
    
    fn main() { }

    When we are done, we want to issue a warning, probably like this:

    fn deref<'x>(v: &'x u32) -> u32 {
    //       ^^ lifetime name `'x` only used once
        *v
    }
    
    fn main() { }

    In this case, the one use of 'x is as the operand to a &-type, so I think we don't have to say much more than that. If the one use of 'x were in a struct, we might want to have a hint indicating that it can be replaced with '_, but let's leave that stuff to future work.

    The first thing we want to do then is to figure out how many times each name is used and where. Now, accounting around lifetimes (e.g., early-bound, late-bound, etc) can get kind of complicated, but luckily we can ignore most of that crap for our purposes. I imagine we would want to add to the LifetimeContext struct a new field:

    lifetime_uses: DefIdMap<LifetimeUseSet<'tcx>

    where a LifetimeUseSet<'tcx> is defined like:

    enum LifetimeUseSet<'tcx> {
        One(&'tcx hir::Lifetime),
        Many,
    }

    We don't really need to track more detail than that -- once there are many uses of a lifetime, we don't want to warn about it anymore, so we don't need to track them. Now, when we are resolving a lifetime in resolve_lifetime_ref, we want to update these lifetime use sets. Actually, I think the place to add code is the insert_lifetime method, which records a successful lifetime resolution:

    fn insert_lifetime(&mut self,
    lifetime_ref: &hir::Lifetime,
    def: Region) {

    Here, we want to match on the def, which is of the type Region. We want to do something like:

    match def {
        LateBoundAnon(..) | Static => {
            // These are anonymous lifetimes or lifetimes that are not declared.
        }
    
        Free(_, def_id) | LateBound(_, def_id) | EarlyBound(_, def_id) => {
            // A lifetime declared by the user.
            if !self.lifetime_uses.contains_key(&def_id) {
                self.lifetime_uses.insert(def_id, LifetimeUseSet::One(lifetime_ref));
            } else {
                self.lifetime_uses.insert(def_id, LifetimeUseSet::Many);
            }
        }
    }

    Now we have a record of where each lifetime def was used. The next step will be going over the set of lifetime names defined and warning if they are LifetimeUseSet::One. I'm going to stop here, but we can talk about the best way to do that later.

  13. 57 remaining items

  14. joshtriplett commented on Aug 26, 2024

    @joshtriplett
    Member

    Summarizing the current state of this:

    I think to make forward progress on this:

    • We need a fix for the ICE, and
    • Either:
      • Lang needs to discuss the GAT issue and reach a consensus, or
      • The stabilization scope needs to reduce to not include that, and lang needs to come to a consensus on the reduced-scope stabilization
  15. added
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    and removed
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    on Dec 21, 2024
  16. zacknewman commented on Jun 23, 2025

    @zacknewman

    Is the following a "false positive"?

    Existing code is the following:

    pub struct Foo<'a>(pub &'a str);
    impl<'a> Foo<'a> {
        // The below lint causes a compilation failure.
        #[deny(single_use_lifetimes)]
        pub fn new<'b: 'a>(x: &'b str) -> Self {
            Self(x)
        }
    }

    Code that uses it looks like below:

    use either::Either;
        
    fn example() {
        Foo::new::<'static>("");
    }
    
    fn example2<I>(s: &str, i: I, b: bool) -> impl Iterator<Item = Foo<'_>>
    where
        I: Iterator<Item = &'static str>,
    {
        if b {
            Either::Left(Some(Foo::new(s)).into_iter())
        } else {
            Either::Right(i.map(Foo::new))
        }
    }

    If Foo::new is changed to accommodate the lint, then example1 and example2 do not compile.

  17. GrigorenkoPV commented on Apr 15, 2026

    @GrigorenkoPV
    Contributor

    Is the following a "false positive"?

    Looks like a false positive to me, yes. Seems like this was also reported in #153836.

  18. added a commit that references this issue on Apr 20, 2026
  19. added a commit that references this issue on Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lifetimesArea: Lifetimes / regionsA-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.B-RFC-approvedBlocker: Approved by a merged RFC but not yet implemented.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCE-mentorCall for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion.L-single_use_lifetimesLint: single_use_lifetimesS-tracking-design-concernsStatus: There are blocking design concerns.S-tracking-impl-incompleteStatus: The implementation is incomplete.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions