Skip to content

Tracking issue for future-incompatibility lint late_bound_lifetime_arguments #42868

Open

Description

@petrochenkov

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; // ERROR

See 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-bound

The precise rules discerning between early- and late-bound lifetimes can be found here:

/// 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

Activity

  1. SimonSapin commented on Jul 19, 2017

    @SimonSapin
    Contributor
    error: cannot specify lifetime arguments explicitly if late bound lifetime parameters are present
       --> /home/travis/build/servo/servo-with-rust-nightly/servo/components/style/stylesheets/stylesheet.rs:243:27
        |
    243 |         self.iter_rules::<'a, 'b, EffectiveRules>(device, guard)
        |                           ^^
        |
    note: lint level defined here
       --> /home/travis/build/servo/servo-with-rust-nightly/servo/components/style/lib.rs:26:9
        |
    26  | #![deny(warnings)]
        |         ^^^^^^^^
        = note: #[deny(late_bound_lifetime_arguments)] implied by #[deny(warnings)]
        = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
        = note: for more information, see issue #42868 <https://github.com/rust-lang/rust/issues/42868>

    What does "late bound" mean in this context? What should we do instead?

  2. petrochenkov commented on Jul 19, 2017

    @petrochenkov
    ContributorAuthor

    @SimonSapin

    What does "late bound" mean in this context?

    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.

    What should we do instead?

    Remove 'a, 'b, , it never did anything anyway.

  3. petrochenkov commented on Jul 19, 2017

    @petrochenkov
    ContributorAuthor

    The error and lint should probably have an extra note pointing to one of these late bound lifetime parameters.

    EDIT: Done in #43343

  4. added a commit that references this issue on Jul 19, 2017
  5. added a commit that references this issue on Jul 21, 2017
  6. 29 remaining items

  7. amanlai commented on Aug 19, 2024

    @amanlai
  8. added
    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.
    A-lifetimesArea: Lifetimes / regions
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Sep 14, 2024
  9. changed the title [-]Tracking issue for `late_bound_lifetime_arguments` compatibility lint[/-] [+]Tracking issue for future-incompatibility lint `late_bound_lifetime_arguments`[/+] on Sep 14, 2024
  10. 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
  11. rkjnsn commented on Feb 11, 2026

    @rkjnsn
    Contributor

    I'm currently running into this generating a wrapper around a method using a macro_rules macro.

    From the function signature passed to the macro, I capture any generics using fn $method:ident $(<$($gen:tt),*>)? (…, generate my wrapper method using fn $method $(<$($gen),*>)? (…, and then call the wrapped method with inner.$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.

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.C-future-incompatibilityCategory: Future-incompatibility lintsC-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCL-late_bound_lifetime_argumentsLint: late_bound_lifetime_argumentsT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language teamT-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions