4
votes

I'm trying to write a small program in Rust but I can't get it work.

I have reproduced the error in a smaller script:

fn main() {
    let name = String::from("World");
    let test = simple(name);
    println!("Hello {}!", test())
}

fn simple<T>(a: T) -> Box<Fn() -> T> {
    Box::new(move || -> T {
        a
    })
}

When I compile it, I get this error:

error[E0310]: the parameter type `T` may not live long enough
  --> test.rs:8:9
   |
7  |       fn simple<T>(a: T) -> Box<Fn() -> T> {
   |                 - help: consider adding an explicit lifetime bound `T: 'static`...
8  | /         Box::new(move || -> T {
9  | |             a
10 | |         })
   | |__________^
   |
note: ...so that the type `[[email protected]:8:18: 10:10 a:T]` will meet its required lifetime bounds
  --> test.rs:8:9
   |
8  | /         Box::new(move || -> T {
9  | |             a
10 | |         })
   | |__________^

I have tried to add an explicit lifetime bound T: 'static as suggested by the error but I get a new error:

error[E0507]: cannot move out of captured outer variable in an `Fn` closure
 --> test.rs:9:13
  |
7 |     fn simple<T: 'static>(a: T) -> Box<Fn() -> T> {
  |                           - captured outer variable
8 |         Box::new(move || -> T {
9 |             a
  |             ^ cannot move out of captured outer variable in an `Fn` closure
2
Have you tried doing what the compiler suggetst (explicit lifetime bound T: 'static)?MB-F
Also note that you cannot move a variable (a) out of a Fn closure - it would no longer be there after the first call.MB-F
What is your goal?ljedrz
When I put the 'static lifetime i get this error: error[E0507]: cannot move out of captured outer variable in an `Fn` closureb1zzu
There are... various solutions, which usually indicates a question that is too broad to answer. I think it's time for you to tell us more about your actual goals :)MB-F

2 Answers

8
votes

There's a couple of things going on here, and it all has to do with a slight awkwardness around move semantics and closures.

First off, the simple function does need to specify a lifetime for its T parameter. From the function's point of view, T can be any type, which means it could be a reference, so it needs to have a lifetime. Lifetime elision doesn't apply to this case so you need to write it out explicitly. The compiler suggests 'static, which is fine for a hello world. If you had more complex lifetimes, you'd need to use a lifetime parameter; see my example below for more.

Your closure can't be a Fn, because you can't call it more than once. As the new error you've got says, your closure moves the value it captures (a) out of the closure when it's called. That's the same thing as saying it's a method that takes self instead of &self. If function calls were a normal method instead of having special syntax, it would be something like this:

trait FnOnce {
    type Output
    fn call(self) -> Output
}

trait Fn : FnOnce {
    fn call(&self) -> Output
}

// generated type
struct MyClosure<T> {
    a: T
}

impl<T> FnOnce for MyClosure<T> {
    fn call(self) -> T { self.a }
}

(This is not that much simpler than the actual definitions of these types.)

So in short, a closure that consumes its captured values doesn't implement Fn, only FnOnce. Calling it consumes the closure. There's also a FnMut but that's not relevant here.

This has another implication, to do with consuming values when they're moved. You might have noticed that you can't call a method that takes self on any trait object (Box<T> where T is a trait). To move an object, the code that's moving it needs to know the size of the object being moved. This doesn't happen with trait objects, which are unsized. That also applies to Box<FnOnce>. Since calling the closure moves it (because calling is a self method`), you can't call the closure.

So how to get around this problem? It makes Box<FnOnce> a bit useless. There's two options.

If you can use unstable Rust, you can use the FnBox type: it's a replacement for FnOnce that works inside a Box. It's hidden behind a feature gate because, as the documentation warns you: "Note that FnBox may be deprecated in the future if Box<FnOnce()> closures become directly usable." Here's a playground that uses this solution and adds lifetime parameters to fix the original problem.

An alternative that might be a more broadly applicable engineering solution would be to avoid moving out of the closure.

  • You could return a reference &'static T if you're always putting static objects into the closure. That way you can call the closure as many times as you like and all callers get a reference to the same object.

  • If the object isn't static, you could instead return a Rc<T>. In this case, all callers still get a reference to the same object, and the lifetime of that object is dynamically managed, so it'll stay alive as long as needed. Here's another playground implementing this option.

  • You could have the closure copy its argument to each caller. This way it could be called as many times as necessary and each caller would get its own copy. No further lifetime management would be necessary. If you implement it this way, you can still make the argument an Rc<T> instead of a T to use the function the same way as the option above.

0
votes

The simple function returns a closure that is generic over the returned type T.

This implies that the returned type may be anything, for example a reference or a type containing references, so the compiler suggests specifying 'static on the type:

fn simple<T: 'static>(a: T) -> Box<Fn() -> T> {
    Box::new(move || -> T { a })
}

but now you have the problem:

error[E0507]: cannot move out of captured outer variable in an `Fn` closure
 --> src/main.rs:2:29
  |
1 | fn simple<T: 'static>(a: T) -> Box<Fn() -> T> {
  |                       - captured outer variable
2 |     Box::new(move || -> T { a })
  |                             ^ cannot move out of captured outer variable in an `Fn` closure

error[E0597]: `name` does not live long enough
 --> src/main.rs:7:24
  |
7 |     let test = simple(&name);
  |                        ^^^^ borrowed value does not live long enough
8 |     println!("Hello {}!", test())
9 | }
  | - borrowed value only lives until here
  |
  = note: borrowed value must be valid for the static lifetime...

Because the captured variable name is owned by the outer "main" context, it cannot be "stolen" by someone else.

The next thing to try is to pass the argument by reference, paying attention to define the lifetime for the boxed Fn trait.

The boxed closure implementing Fn trait lives on the heap and the correct lifetime has to be explicitly assigned: Fn() -> &'a T` + 'a

fn main() {
    let name = String::from("World");
    let test = simple(&name);
    println!("Hello {}!", test())
}

fn simple<'a, T: 'a>(val: &'a T) -> Box<Fn() -> &'a T + 'a> {
    Box::new(move || -> &'a T { val })
}

Another solution is to use impl trait, available as of Rust 1.26:

fn main() {
    let name = String::from("World");
    let test = simple(&name);
    println!("Hello {}!", test())
}

fn simple<'a, T: 'a>(val: &'a T) -> impl Fn() -> &'a T {
    move || -> &'a T { val }
}