8
votes

I am trying to write a function returning a boxed closure that can work on references to types with any lifetime. When writing a specific instance, everything works fine. But when writing a generic version, I run into lifetime problems.

struct Parameter<'a> {
    s: &'a str,
}

fn main() {
    let closure = generate_closure_gen();
    let string = String::from("Hello World!");
    let parameter = Parameter { s: &string }; // Error: string does not live long enough
    closure(&parameter);
}

// This one works fine
// Desugared version for Box<Fn(&Parameter)>
fn generate_closure() -> Box<for <'a, 'r> Fn(&'r Parameter<'a>)> {
    Box::new(|c: &Parameter| {})
}

// This one gives lifetime errors
fn generate_closure_gen<C>() -> Box<Fn(&C)> {
    Box::new(|c: &C| {})
}

I don't see why the closure needs the type parameter to live longer than it (there is no storage or anything ...). And it works for the non-generic version with HRTB, it just feels like it should be possible to make it work with the generic version.
Also, if I try to write the specific version using the generic version, I get a type error

// Desugared version for Box<Fn(&Parameter)>
fn generate_closure_2() -> Box<for <'a, 'r> Fn(&'r Parameter<'a>)> {
    generate_closure_gen()
}

src/main.rs:22:5: 22:27 error: mismatched types:
 expected `Box<for<'r, 'r> core::ops::Fn(&'r Parameter<'r>) + 'static>`,
    found `Box<for<'r> core::ops::Fn(&'r _) + 'static>`
(expected concrete lifetime,
    found bound lifetime parameter ) [E0308]
src/main.rs:22     generate_closure_gen()
                   ^~~~~~~~~~~~~~~~~~~~~~
src/main.rs:22:5: 22:27 help: run `rustc --explain E0308` to see a detailed explanation

Any idea on how to make this work?

(playpen link)

1
The compiler does not seem to insert the for<'a> by itself when given a generic parameter. If you do not receive any satisfying explanation, you might want to post a link to this question on reddit.com/r/rust where Rust developers are more likely to hang around.Matthieu M.
My answer was obviously wrong, so I deleted it. But your comment is explaining what exactly do you mean: "I don't see why the closure needs the type parameter to live longer than it (there is no storage or anything ...). And it works for the non-generic version with HRTB, it just feels like it should be possible to make it work with the generic version." – @VaeldenaSpex

1 Answers

7
votes

Type parameters have a lifetime bound. That lifetime bound is the shortest of all of the implementor's lifetime parameters. You omitted it on generate_closure_gen, so the compiler inferred it, but if we explicitly wrote it out, the function definition would look like this:

fn generate_closure_gen<'a, C: 'a>() -> Box<Fn(&C)> {
    Box::new(|c: &C| {})
}

Making this change doesn't solve our problem, though.

To understand why, we need to figure out what C is inferred to be. You call the closure with a &'y Parameter<'x>, and the closure accepts for<'b> &'b C, so C is Parameter<'x>. Parameter<'x> has a lifetime parameter, which will have an influence on the lifetime bound on C.

Lifetime parameters in generic functions must be substituted with lifetimes that start before the function call. In this case, this means that the lifetime of any C we pass to the closure must be valid before the call to generate_closure_gen. That's because C is bound to a specific lifetime, not to any lifetime; i.e. when C is Parameter<'x>, the 'x must be known in advance; we can't have a different 'x each time we call the closure. In other words, what you'd like to have is something like this:

fn generate_closure_gen<C: for<'a> 'a>() -> Box<Fn(&C)> {
    Box::new(|c| {})
}

But unfortunately, that isn't legal as of Rust 1.7.