Repository navigation
Tracking issue for future-incompatibility lint late_bound_lifetime_arguments #42868
Description
What is this lint about
In functions not all lifetime parameters are created equal.
For example, if you have a function like
fn f<'a, 'b>(arg: &'a u8) -> &'b u8 { .... }both 'a and 'b are listed in the same parameter list, but when stripped from the surface syntax the function looks more like
for<'a> fn f<'b>(arg: &'a u8) -> &'b u8 { .... }where 'b is a "true" ("early-bound") parameter of the function, and 'a is an "existential" ("late-bound") parameter. This means the function is not parameterized by 'a.
To give some more intuition, let's write a type for function pointer to f:
// PtrF is not parameterized by 'a,
type PtrF<'b> = for<'a> fn(&'a u8) -> &'b u8;
// but it has to be parameterized by 'b
type PtrF = for<'a, 'b> fn(&'a u8) -> &'b u8; // ERRORSee more about this distinction in http://smallcultfollowing.com/babysteps/blog/2013/10/29/intermingled-parameter-lists/#early--vs-late-bound-lifetimes
When lifetime arguments are provided to a function explicitly, e.g.
f::<'my_a, 'my_b>
the first argument doesn't make much sense because the function is not parameterized by 'a.
Providing arguments for "late-bound" lifetime parameters in general doesn't make sense, while arguments for "early-bound" lifetime parameters can be provided.
It's not clear how to provide arguments for early-bound lifetime parameters if they are intermixed with late-bound parameters in the same list. For now providing any explicit arguments is prohibited if late-bound parameters are present, so in the future we can adopt any solution without hitting backward compatibility issues.
Note that late-bound lifetime parameters can be introduced implicitly through lifetime elision:
fn f(&u8) {}
// Desugars into
fn f<'a>(&'a u8) {} // 'a is late-boundThe precise rules discerning between early- and late-bound lifetimes can be found here:
rust/src/librustc/middle/resolve_lifetime.rs
Lines 1541 to 1700 in 91aff57
| /// Detects late-bound lifetimes and inserts them into | |
| /// `map.late_bound`. | |
| /// | |
| /// A region declared on a fn is **late-bound** if: | |
| /// - it is constrained by an argument type; | |
| /// - it does not appear in a where-clause. | |
| /// | |
| /// "Constrained" basically means that it appears in any type but | |
| /// not amongst the inputs to a projection. In other words, `<&'a | |
| /// T as Trait<''b>>::Foo` does not constrain `'a` or `'b`. | |
| fn insert_late_bound_lifetimes(map: &mut NamedRegionMap, | |
| fn_def_id: DefId, | |
| decl: &hir::FnDecl, | |
| generics: &hir::Generics) { | |
| debug!("insert_late_bound_lifetimes(decl={:?}, generics={:?})", decl, generics); | |
| let mut constrained_by_input = ConstrainedCollector { regions: FxHashSet() }; | |
| for arg_ty in &decl.inputs { | |
| constrained_by_input.visit_ty(arg_ty); | |
| } | |
| let mut appears_in_output = AllCollector { | |
| regions: FxHashSet(), | |
| impl_trait: false | |
| }; | |
| intravisit::walk_fn_ret_ty(&mut appears_in_output, &decl.output); | |
| debug!("insert_late_bound_lifetimes: constrained_by_input={:?}", | |
| constrained_by_input.regions); | |
| // Walk the lifetimes that appear in where clauses. | |
| // | |
| // Subtle point: because we disallow nested bindings, we can just | |
| // ignore binders here and scrape up all names we see. | |
| let mut appears_in_where_clause = AllCollector { | |
| regions: FxHashSet(), | |
| impl_trait: false | |
| }; | |
| for ty_param in generics.ty_params.iter() { | |
| walk_list!(&mut appears_in_where_clause, | |
| visit_ty_param_bound, | |
| &ty_param.bounds); | |
| } | |
| walk_list!(&mut appears_in_where_clause, | |
| visit_where_predicate, | |
| &generics.where_clause.predicates); | |
| for lifetime_def in &generics.lifetimes { | |
| if !lifetime_def.bounds.is_empty() { | |
| // `'a: 'b` means both `'a` and `'b` are referenced | |
| appears_in_where_clause.visit_lifetime_def(lifetime_def); | |
| } | |
| } | |
| debug!("insert_late_bound_lifetimes: appears_in_where_clause={:?}", | |
| appears_in_where_clause.regions); | |
| // Late bound regions are those that: | |
| // - appear in the inputs | |
| // - do not appear in the where-clauses | |
| // - are not implicitly captured by `impl Trait` | |
| for lifetime in &generics.lifetimes { | |
| let name = lifetime.lifetime.name; | |
| // appears in the where clauses? early-bound. | |
| if appears_in_where_clause.regions.contains(&name) { continue; } | |
| // any `impl Trait` in the return type? early-bound. | |
| if appears_in_output.impl_trait { continue; } | |
| // does not appear in the inputs, but appears in the return | |
| // type? eventually this will be early-bound, but for now we | |
| // just mark it so we can issue warnings. | |
| let constrained_by_input = constrained_by_input.regions.contains(&name); | |
| let appears_in_output = appears_in_output.regions.contains(&name); | |
| if !constrained_by_input && appears_in_output { | |
| debug!("inserting issue_32330 entry for {:?}, {:?} on {:?}", | |
| lifetime.lifetime.id, | |
| name, | |
| fn_def_id); | |
| map.issue_32330.insert( | |
| lifetime.lifetime.id, | |
| ty::Issue32330 { | |
| fn_def_id, | |
| region_name: name, | |
| }); | |
| continue; | |
| } | |
| debug!("insert_late_bound_lifetimes: \ | |
| lifetime {:?} with id {:?} is late-bound", | |
| lifetime.lifetime.name, lifetime.lifetime.id); | |
| let inserted = map.late_bound.insert(lifetime.lifetime.id); | |
| assert!(inserted, "visited lifetime {:?} twice", lifetime.lifetime.id); | |
| } | |
| return; | |
| struct ConstrainedCollector { | |
| regions: FxHashSet<ast::Name>, | |
| } | |
| impl<'v> Visitor<'v> for ConstrainedCollector { | |
| fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'v> { | |
| NestedVisitorMap::None | |
| } | |
| fn visit_ty(&mut self, ty: &'v hir::Ty) { | |
| match ty.node { | |
| hir::TyPath(hir::QPath::Resolved(Some(_), _)) | | |
| hir::TyPath(hir::QPath::TypeRelative(..)) => { | |
| // ignore lifetimes appearing in associated type | |
| // projections, as they are not *constrained* | |
| // (defined above) | |
| } | |
| hir::TyPath(hir::QPath::Resolved(None, ref path)) => { | |
| // consider only the lifetimes on the final | |
| // segment; I am not sure it's even currently | |
| // valid to have them elsewhere, but even if it | |
| // is, those would be potentially inputs to | |
| // projections | |
| if let Some(last_segment) = path.segments.last() { | |
| self.visit_path_segment(path.span, last_segment); | |
| } | |
| } | |
| _ => { | |
| intravisit::walk_ty(self, ty); | |
| } | |
| } | |
| } | |
| fn visit_lifetime(&mut self, lifetime_ref: &'v hir::Lifetime) { | |
| self.regions.insert(lifetime_ref.name); | |
| } | |
| } | |
| struct AllCollector { | |
| regions: FxHashSet<ast::Name>, | |
| impl_trait: bool | |
| } | |
| impl<'v> Visitor<'v> for AllCollector { | |
| fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'v> { | |
| NestedVisitorMap::None | |
| } | |
| fn visit_lifetime(&mut self, lifetime_ref: &'v hir::Lifetime) { | |
| self.regions.insert(lifetime_ref.name); | |
| } | |
| fn visit_ty(&mut self, ty: &hir::Ty) { | |
| if let hir::TyImplTrait(_) = ty.node { | |
| self.impl_trait = true; | |
| } | |
| intravisit::walk_ty(self, ty); | |
| } | |
| } | |
| } |
How to fix this warning/error
Just removing the lifetime arguments pointed to by the lint should be enough in most cases.
Current status
- Support generic lifetime arguments in method calls #42492 introduces the
late_bound_lifetime_argumentslint as warn-by-default - PR ? makes the
late_bound_lifetime_argumentslint deny-by-default - PR ? makes the
late_bound_lifetime_argumentslint a hard error
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsIdea
Activity
SimonSapin commentedon Jul 19, 2017
What does "late bound" mean in this context? What should we do instead?
petrochenkov commentedon Jul 19, 2017
@SimonSapin
Well, that's a good question, but not something that can be explained in an error message.
See for example http://smallcultfollowing.com/babysteps/blog/2013/10/29/intermingled-parameter-lists/#early--vs-late-bound-lifetimes. I believe nomicon had something about it, but I can't find it right now.
This issue should have a description, and @nikomatsakis is probably the best person to write it, but I can try as well.
Remove
'a, 'b,, it never did anything anyway.petrochenkov commentedon Jul 19, 2017
The error and lint should probably have an extra note pointing to one of these late bound lifetime parameters.
EDIT: Done in #43343
Remove explicit lifetime arguments on a method call.
Auto merge of #17785 - servo:future-break, r=nox
servo: Merge #17785 - Remove explicit lifetime arguments on a method …
servo: Merge #17785 - Remove explicit lifetime arguments on a method …
servo: Merge #17785 - Remove explicit lifetime arguments on a method …
Rollup merge of rust-lang#43343 - petrochenkov:methlife3, r=estebank
29 remaining items
Remove ContentsLayout and use trait objects.
amanlai commentedon Aug 19, 2024
A related Stack Overflow discussion.
[-]Tracking issue for `late_bound_lifetime_arguments` compatibility lint[/-][+]Tracking issue for future-incompatibility lint `late_bound_lifetime_arguments`[/+]servo: Merge #17785 - Remove explicit lifetime arguments on a method …
rkjnsn commentedon Feb 11, 2026
I'm currently running into this generating a wrapper around a method using a
macro_rulesmacro.From the function signature passed to the macro, I capture any generics using
fn $method:ident $(<$($gen:tt),*>)? (…, generate my wrapper method usingfn $method $(<$($gen),*>)? (…, and then call the wrapped method withinner.$method $(::<$($gen),*>)? (…. I just want to pass along all generic types and lifetimes as specified / inferred at the call site. This works fine today, but gives me this warning-someday-to-be-error if the signature has any late bound lifetimes arguments.