Factory of closures - lifetime of closure's return value


#1

Hello
I’m trying to understand Rust by combining language’s things i have just learned.
Now i’m trying to implement “factory of closures”, but have some difficulty with lifetimes.

This code works well:

fn main() {
    let mut xlations: Vec<String> = Vec::new();

    ... // here the vector is filled with values

    let closure = closure_factory(xlations); // construct closure

    println!("closure(0)={:?}", closure(0));
    println!("closure(1)={:?}", closure(1));
    println!("closure(100)={:?}", closure(100));
    // println!("original vector = {:?}", xlations);  // xlations was moved and can't be used here
}

fn closure_factory(xlat: Vec<String>) -> Box<Fn(usize)->String> {
    Box::new(move |i| if i<xlat.len() { xlat[i].clone() } else { "".to_string() } )
}

First, ownership of argument (vector of Strings) is moved into factory function, then it is moved into newly constructed closure. Then, boxed closure is returned from factory. Finally, returned closure is the owner of original vector.

There is some redundancy here, i have to clone strings before resulting. But if i try return a reference

fn closure_factory(xlat: Vec<String>) -> Box<Fn(usize)->&str> {
    Box::new(move |i| if i<=xlat.len() { &xlat[i] } else { "" })
}

i get a compilation error:

error[E0106]: missing lifetime specifier
  --> .\closures.rs:63:56
   |
63 | fn closure_factory(xlat: Vec<String>) -> Box<Fn(usize)->&str> {
   |                                                        ^ expected lifetime parameter
   |
   = help: this function's return type contains a borrowed value with an elided lifetime, but the lifetime cannot be derived from the arguments
   = help: consider giving it an explicit bounded or 'static lifetime

error: aborting due to previous error

But i can’t realise how to specify a lifetime that is equivalent to lifetime of closure’s own stack frame (the closure’s stack frame is the final owner of vector, isn’t?).


#2

I don’t think it’s possible. As a workaround, you could pass a reference to the vector into the closure, and tie the lifetime of the returned &str's to the lifetime of that reference:

    let closure = closure_factory(&xlations);
    ...
fn closure_factory<'a>(xlat: &'a[String]) -> Box<Fn(usize) -> &'a str + 'a> {
    Box::new(move |i| if i < xlat.len() { &xlat[i] } else { "" })
}

Adding the 'a bound to the closure is necessary if it’s to contain stack references (the default is 'static which precludes that).


#3

You can’t return a lifetime limited to your own stack frame, because by the time you return, the stack frame is already gone!

Edit: I now realize you want to specify the lifetime of the closed-over variables. I guess this should technically be possible, but I’m not sure if it actually is.


#4

Box<Fn(usize) -> &'a str + 'a>

inejge, can you help me understand this return type. What is the “+ 'a” doing here?


#5

Because you’re returning a trait object (the Fn is in a Box) you need to tell the compiler that the true type underneath (i.e. the anonymous closure type in this particular example) won’t have any references that live shorter than 'a. If that were allowed to happen, the trait object could deref invalid/freed memory.

The default trait object bounds are 'static, so it wouldn’t let you store &'a str in it - the “+ 'a” bound allows it.

Edit: put another way, it’s saying the trait object doesn’t outlive 'a.


#6

Thanks for the overview. I think I get it.


#7

Thanks to all replyers.

jethrogb
I now realize you want to specify the lifetime of the closed-over variables.
I guess this should technically be possible, but I’m not sure if it actually is.

That is it. I have also tried to manually emulate “closure with moved(owned) environment variables” using struct as container for “closed-over variables” and ‘call(&self)’ method on this struct (just as simple method, not as trait Fn, i still don’t understand well how to do this). With such manually implemented “closure” i have got all necessary behavior and lifitimes. It is strange, why such behavior can’t be got with standard closure.