2

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.

Eloff
  • 20,828
  • 17
  • 83
  • 112
  • 3
    See also [Lifetime of references in closures](https://stackoverflow.com/q/42747620/155423); [Is there a way to have a Rust closure that moves only some variables into it?](https://stackoverflow.com/q/58459643/155423). – Shepmaster Sep 23 '20 at 16:46
  • 1
    [The compiler suggests I add a 'static lifetime because the parameter type may not live long enough, but I don't think that's what I want](/q/40053550/3650362) may help some of the confusion around the meaning of `'static` here. – trent Sep 23 '20 at 17:03

1 Answers1

6

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.

trent
  • 25,033
  • 7
  • 51
  • 90
user1937198
  • 4,987
  • 4
  • 20
  • 31
  • So move closures copy references into the closure, basically just copying the pointer bits themselves and not automatically referencing and moving the referenced struct. That's what I thought must be happening, but I couldn't verify it from the rust reference. – Eloff Sep 23 '20 at 17:14
  • 2
    @Eloff If you're familiar with other languages you might be used to thinking of "references" as different from "objects", and having a dichotomy between operations that act on the reference vs. operations that act on the object. There's no such dichotomy in Rust: references are just regular objects that happen to be of reference type. So a `move` closure that uses `foo` will always move `foo`, whether it's a reference or not. – trent Sep 23 '20 at 17:45