Lifetime of arguments to Fn

I'm trying to make the following compile (without getting rid of the Bar trait alias):

trait_set::trait_set! {
    pub trait Bar =  Fn(&[u32]);
}
/* Expands to
    pub trait Bar: Fn(&[u32]) {}
    impl<T: Fn(&[u32])> Bar for T {}
*/

fn foo(bar: impl Bar) {
    bar(&vec![1]);
}

fn main() {
    foo(|a| println!("{:?}", a));
}

Compiling the above code as-is yields the following error:

error[E0308]: mismatched types
  --> src/main.rs:10:5
   |
10 |     foo(|a| println!("{:?}", a));
   |     ^^^ lifetime mismatch
   |
   = note: expected type `for<'r> Fn<(&'r [u32],)>`
              found type `Fn<(&[u32],)>`
// [...]

If I understand the error message correctly, then the problem is that the Bar trait requires the implementing function to take a reference with any arbitrary lifetime, while the closure that I pass to foo() only accepts a reference with lifetime equal to the duration of the function call. So I believe my question is, how can I tell Rust that the latter lifetime requirement is good enough for my Bar trait?

Btw, it's worth noting that this compiles:

fn foo(bar: impl Fn(&[u32])) {
    bar(&vec![1]);
}

fn main() {
    foo(|a| println!("{:?}", a));
}

So the problem only arises if I move the Fn(...) bound into a trait alias.

|a: &_| usually works?

It's inferring that the closure that you pass takes only one specific lifetime, instead of any arbitrary lifetime (aka "being higher-ranked"). Said specific lifetime could be a variety of lengths, depending on the context.


The compiler currently has some problems inferring that closures are higher-ranked sometimes (often really). For this particular example, you can nudge it the right direction with some annotation on the argument:

    foo(|a: &_| println!("{:?}", a));

More generally, until the linked RFC stabilizes, you can use an identity function to supply a coercion site.

2 Likes

I guess there's no way to tell the compiler not to introduce a higher-ranked lifetime in the first place? I would have hoped that something like

trait_set::trait_set! {
    pub trait Bar =  Fn(&'_ [u32]);
}

or

trait_set::trait_set! {
    pub trait Bar<'a> =  Fn(&'a [u32]);
}

fn foo(bar: impl Bar<'_>) {
    bar(&vec![1]);
}

would work, where '_ is meant to denote the lifetime of the current function invocation (still not quite sure what '_ really means, or where to find proper documentation for it).

For closures generally, you can use the same type of identity function workarounds. See the step-by-step section here. A dedicated syntax to complement for<'a> |...| is deferred for now.

That said, needing to force a closure to capture a singular region instead of being higher-ranked is much less common than the opposite.

Also, it wouldn't help for your use case (continued below).

It generally means the same as leaving it off. [1] What leaving it off does depends on the context. For impl Trait for T headers and function input arguments [2], it implies a fresh (higher-ranked) lifetime; for output arguments, it ties it to a specific input parameter when unambiguous. In static and const declarations, it's generally 'static. For types and non-Fn() like traits and in turbofish, it generally means "infer this singular lifetime for me from context"; however in where clauses, it's not allowed, and in function argument position, it's also not allowed for non-Fn() impl Trait. For dyn Trait... let's not even go there.

So yeah, the elision rules are complicated, very context dependent, and all over the place. The language has gone for "what you probably meant 90% of the time" in this regard and the rules have developed organically. I'm unaware of a consolidated reference on elision.

trait_set::trait_set! {
    pub trait Bar<'a> =  Fn(&'a [u32]);
}

fn foo(bar: impl Bar<'_>) {
    bar(&vec![1]);
}

for<'a> Fn(&'a [u32]), aka Fn(&[u32]) is the closest thing there is to "the lifetime of the current invocation only", or "arbitrarily small". There is no way to name or imply such a singular lifetime. [3] When your trait bound is not higher-ranked:

pub trait Bar<'x>: Fn(&'x [u32]) {}
impl<'a, T: Fn(&'a [u32])> Bar<'a> for T {}

fn foo<'x>(bar: impl Bar<'x>) {
    bar(&vec![1]);
}

Then that typically means "any singular lifetime is supported, and the caller chooses which to use." This doesn't work because the caller could call foo::<'static>(|_| {}), say.

So higher-ranked is probably what you do want.


  1. Mind the note at the top of this RFC - "in-band (non-declared) lifetimes" were unaccepted. ↩︎

  2. including Fn() traits and fn() function pointer types ↩︎

  3. It wouldn't really be singular per se: what happens when you call such a method twice in the same inference environment/temporary lifetime scope? ↩︎

1 Like