2
votes

This code does not compile without adding move to the closure. It produces the error:

error[E0373]: closure may outlive the current function, but it borrows `foo`, which is owned by the current function
  --> src/main.rs:26:18
   |
26 |     do_something(|| {
   |                  ^^ may outlive borrowed value `foo`
27 |         foo.bar += 1;
   |         --- `foo` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src/main.rs:26:5
   |
26 | /     do_something(|| {
27 | |         foo.bar += 1;
28 | |         println!("{}", foo.bar);
29 | |     });
   | |______^
help: to force the closure to take ownership of `foo` (and any other referenced variables), use the `move` keyword
   |
26 |     do_something(move || {
   |                  ^^^^^^^

This error is confusing, as it seems to imply a lifetime longer than 'static is required, as if anything could outlive 'static. Here foo is a 'static reference to a struct. If I add move to the closure, isn't it going to move the referenced struct itself into the closure? Or is it just copying the reference (the pointer bits)? It seems to me that it's just moving the reference, not Foo itself. My understanding is very fuzzy about how move closures work with respect to references.

struct Foo {
    bar: i32,
}

impl Drop for Foo {
    fn drop(&mut self) {
        panic!("dropping Foo, should never happen");
    }
}

pub fn do_something<F, T>(mut f: F)
where
    F: FnMut() -> T + 'static,
    T: 'static,
{
    // "spawn" 3 threads
    f();
    f();
    f();
}

fn main() {
    let foo = Box::leak::<'static>(Box::new(Foo { bar: 0 }));
    let raw_ptr: *const Foo = foo;

    do_something(move || {
        foo.bar += 1;
        println!("{}", foo.bar);
    });

    println!("{}", unsafe { (*raw_ptr).bar });
}

This question is similar to Specifying static lifetime of value in `main` so callback can borrow but I don't think it's a duplicate.

1

1 Answers

6
votes

move causes the value of any captured variable to be moved, whilst if move is not specified, and the closure does not consume the variable, the closure will try to take a reference to it instead. Although the variable is captured by reference, within the closure the name still acts as if it were the original variable.

The key point of confusion here comes from the fact foo has type &mut Foo, not Foo, which are distinct types.

So in this case, without the move a reference to foo is created with type &'a mut &'static mut Foo, where 'a is the lifetime of foo within the main method. This causes an error as it restricts the lifetime of the closure to less than the 'static required by do_something.