Borrowed data escapes outside of closure

Cannot understand, why not compile:

fn main() {
    let mut a = vec![];
    let mut b = vec![];

    let mut f = || {
        //let mut b = vec![]; // No error
        a.push(1);
        b.push(a.last().unwrap()); // borrowed data escapes outside of closure
    };

    f();
}

Lifetime of a vec is same as b. Isn't it?

The issue is that, after the call to f, the vector b will borrow from a because it contains a reference into a, but since neither a nor b appear in the call f();, calling that function cannot create such a borrow between two variables not mentioned in the call at all.

1 Like

Oddly, it behaves as if it moved a, but borrowed b. I'm not sure why. This fixes it:

fn main() {
    let mut a = vec![];
    let mut b = vec![];

    let mut f = move || {
        let mut b = b; // force move of b
        a.push(1);
        b.push(a.last().unwrap()); // it's fine now
    };

    f();
}

However, the original half-move-half-borrow situation compiler assumed is actually unsafe. Consider:

fn main() {
    let mut a = vec![];
    let b = &mut vec![];

    let mut f = move || {
        a.push(1);
        b.push(a.last().unwrap()); // borrowed data escapes outside of closure
    };

    f();

   drop(f); // a is gone now
   println!("{:?}", b); // b has use-after-free
}
1 Like

Same compile error with your second example with borrowing.

To add to @alice's reply, a has type Vec<u32> whereas b has type Vec<&u32>. You probably wanted both to be Vec<u32> instead of having b borrow from a.

fn main() {
    let mut a = vec![];
    let mut b = vec![];

    let mut f = || {
        a.push(1);
        // `copied` converts `Option<&T>` to `Option<T>` where `T: Copy`.
        b.push(a.last().copied().unwrap());
    };

    f();
}

This compiles fine.

2 Likes

My understanding is that

    let mut f = || {
        //let mut b = vec![]; // No error
        a.push(1);
        b.push(a.last().unwrap()); // borrowed data escapes outside of closure
    };

would like to borrow a and b mutably. But also, it re-borrows the mutable borrow immutably for the cal to a.last() inside of the closure. This re-borrow is what is not allowed to escape the closure.

But this also seems to be related to the fact that the closure, only capturing variables by reference, wants to implement FnMut. If you convince the compiler that FnMut is not an option, then everything compiles:

+ struct NotCopy;

fn main() {
    let mut a = vec![];
    let mut b = vec![];

+   let unrelated = NotCopy;
    let mut f = || {
+       let _unrelated = unrelated; // <- do some stuff preventing FnMut
        a.push(1);
        b.push(a.last().unwrap());
    };

    f();

+   dbg!(&b); // none of these
+   dbg!(&a); // has been moved
}

It’s a different story if the borrow of a is immutable, since the re-borrow is avoided, so the closure can implement FnMut without any problem:

fn main() {
    let mut a = vec![];
    let mut b = vec![];
+   a.push(1); // avoid panicking on `unwrap` and help out the type-checker

    let mut f = || {
-       a.push(1);
        b.push(a.last().unwrap());
    };

    f();
+   f(); // can be called multiple times now

+   dbg!(&b); // none of these
+   dbg!(&a); // has been moved
}
5 Likes

So why then compiler wants FnMut if Fn is enough?

The compiler doesn't know from the outset which Fn traits will apply -- it uses a simple heuristic based on how variables from the environment are used when the function is called. The heuristic can be wrong, and then the error will not be discovered until the borrow checking stage -- too late to go back and change the initial assumption.

2 Likes

You might be misunderstanding Fn traits here. There are three traits: Fn, FnMut and FnOnce. Here’s some facts:

  • all closures implement FnOnce
  • some closures implement FnMut
    • every closure that can be called multiple times implements FnMut
    • every closure that implements FnMut can be called multiple times
  • some closures that implement FnMut also implement Fn, others implement FnMut but don’t implement Fn
  • every closure that implements Fn also implements FnMut
  • a closure that cannot be called multiple times does not implement FnMut and does not implement Fn. It only implements FnOnce

In particular the question “why then compiler wants FnMut if Fn is enough” does not make too much sense, since a Fn implementation always implies a FnMut implementation.

In my code example, the line let _unrelated = unrelated prevents the closure from implementing FnMut, as I said, i.e.

  • without that line, the closure would implement FnMut and FnOnce and would not implement Fn
    • it would never implement Fn since the captured variables a and b are mutated by the closure.
  • with that line added, the closure implements FnOnce only

As I explained above,

there’s a heuristic, as @trentj mentioned too, that whenever a (non move) closure does not captures any value by moving out of it, i.e. as long as the code inside of a closure does not move out of any captured variable, the closure will want to implement FnOnce and also FnMut. The general idea being that generally it the case that a closure that doesn’t move out of any of its captured variables can be called multiple times, whereas moving out of a captured variable can only happen once, hence the closure would not implement FnMut. The line let _unrelated = unrelated moves out of the captured unrelated variable, since it uses this variable by value and the variable’s type does not implement Copy.

Of course, as demonstrated by your code example and the modification in my post, it is not true that moving out of a captured variable is the only thing that can prevent a closure from being able to be called multiple times. The borrow checker can also be unhappy with the FnMut implementation while accepting the FnOnce implementation of a closure.

2 Likes

Since closures are essentially compiler-created structs, it might help to do the desugaring ourselves and see where it goes wrong:

fn main() {
    let mut a = vec![];
    let mut b = vec![];
    
    // this represents the closure type created implicitly by the compiler
    struct Closure<'a, 'b> {
        // these references are inferred based on how `a` and `b` are used in the closure
        a: &'a mut Vec<i32>,
        b: &'b mut Vec<&'a i32>,
    }
    
    impl<'a, 'b> Closure<'a, 'b> {
        // this is how FnOnce can be implemented:
        fn call_once(self) {
            // `a` is moved out of `self`, so it can have the lifetime `'a`
            let Closure { mut a, mut b } = self;
            a.push(1);
            b.push(a.last().unwrap());
        }
        
        // this is what an attempt to implement `FnMut` might look like:
        fn call_mut(&mut self) {
            // `a` is re-borrowed with the lifetime of `self`, which is why a borrow
            // derived from it cannot be pushed into `b`
            let Closure { ref mut a, ref mut b } = *self;
            a.push(1);
            b.push(a.last().unwrap()); // error[E0495]: cannot infer an appropriate lifetime...
        }
    }

    // this is what the line containing the closure actually does
    let mut f = Closure {
        a: &mut a,
        b: &mut b,
    };

    f.call_once();
}

Playground. If you delete fn call_mut it will compile.

The actual error message is different in your original code because there is a specific error message for closures, but I'm pretty sure this is the same analysis it's doing under the hood. (It's also possible I've messed up call_mut somehow)

5 Likes

Thanks. Now i understand.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.