Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Higher Ranked Trait Bound and boxed closures lifetime issue

Tags:

rust

lifetime

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)

like image 360
Vaelden Avatar asked Mar 01 '16 00:03

Vaelden


1 Answers

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.

like image 107
Francis Gagné Avatar answered Oct 19 '22 03:10

Francis Gagné