Repository navigation
In-band lifetimes: Lint against single-use lifetime names #44752
Description
Activity
- addedE-mentorCall for participation: This issue has a mentor. Use #t-compiler/help on Zulip for discussion.Call 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.Relevant to the compiler team, which will review and decide on the PR/issue.
on Sep 21, 2017 - addedC-enhancementCategory: An issue proposing an enhancement or a PR with one.Category: An issue proposing an enhancement or a PR with one.
on Sep 21, 2017 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
@cramertj Hmm. I would consider the proper way to declare
bar()to probably befn bar() -> &'static u8, really, though there are subtleties involved in rare cases (e.g., around invariance).@nikomatsakis Yes,
'staticis the right thing to use. I wanted to point out that we shouldn't recommend'_here, as it won't work.- changed the title
[-]Lint against single-use lifetime names[/-][+]In-band lifetimes: Lint against single-use lifetime names[/+]on Sep 25, 2017 Is this up for grabs?
@gaurikholkar Go for it!
Will start working on it
@gaurikholkar hey, just checking in! How's it going? Any blockers?
Some examples:
fn foo<'x>(_: &'x u32) { }
This should lint against
'xand suggest&u32instead.struct Foo<'a> { x: &'a u32 } fn foo<'x>(_: Foo<'x>) { }
This should lint against
'xand suggestFoo<'_>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
'yand suggestFoo<'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
'xand suggestFoo<'_, 'y>instead.fn foo<'x>() -> &'x u32 { &22 }
This should not lint, because
'xappears 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).Reacted by Gauri KholkarOK, 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
'xis as the operand to a&-type, so I think we don't have to say much more than that. If the one use of'xwere 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
LifetimeContextstruct 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 theinsert_lifetimemethod, which records a successful lifetime resolution:rust/src/librustc/middle/resolve_lifetime.rs
Lines 1516 to 1518 in 3cf28f3
fn insert_lifetime(&mut self, lifetime_ref: &hir::Lifetime, def: Region) { Here, we want to match on the
def, which is of the typeRegion. 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.57 remaining items
Summarizing the current state of this:
- Always create elided lifetime parameters for functions #97720 was merged, but it requires the unstable feature
anonymous_lifetime_in_impl_trait anonymous_lifetime_in_impl_traitdoesn't have a tracking issue, but Stabilizeanonymous_lifetime_in_impl_trait#107378 proposes stabilization of it.- Stabilize
anonymous_lifetime_in_impl_trait#107378 is blocked on a concern about interaction with GATs. There are some suggestions to reduce the scope of what's being stabilized, but I wasn't clear on the state of that. anonymous_lifetime_in_impl_traitalso has an ICE: ICE with anonymous_lifetime_in_impl_trait feature: "calledOption::unwrap()on aNonevalue" #124340
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
- Always create elided lifetime parameters for functions #97720 was merged, but it requires the unstable feature
- addedA-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.Area: Lints (warnings about flaws in source code) such as unused_mut.and removedA-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.Area: Lints (warnings about flaws in source code) such as unused_mut.
on Dec 21, 2024 - addedT-langRelevant to the language teamRelevant to the language teamS-tracking-design-concernsStatus: There are blocking design concerns.Status: There are blocking design concerns.
on Mar 23, 2025 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::newis changed to accommodate the lint, thenexample1andexample2do not compile.Is the following a "false positive"?
Looks like a false positive to me, yes. Seems like this was also reported in #153836.
- added a commit that references this issue
on Apr 20, 2026 - added a commit that references this issue
on Apr 21, 2026
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:
unused_lifetimes'aappearing in&'a T, suggest&T'aappearing in any other place, suggest'_Older Background
The idea is that an explicit name like
'ashould 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_lifetimescode: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 fromvisit_early_late).resolve_lifetime_ref()is called to resolve an actual reference to a named lifetime.Scope, e.g. counting how many times a particular lifetime was referenced.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.)