rustclosuresborrow-checker

Why does this code that moves a closure into another closure and returns both even compile?


I have the following code:

fn test() -> (impl FnMut(&mut u8), impl FnMut(&mut u8)) {
    let a = |v: &mut u8| {*v = 0};
    
    let b = move |v: &mut u8| { a(v); println!("{}", v) };
    
    (a, b)
}

fn main() {
    let mut val: u8 = 10;
    let (mut a, mut b) = test();
    b(&mut val);
    val = 10;
    a(&mut val);
    assert!(val == 0);
}

(this is a MWE based on something I encountered in the wild). Now, this works as expected, but I don't really understand why this even compiles: We need to move a into b to use it there (otherwise, one gets a compiler error), but somehow, we can still return a afterwards and use it without any issue. Doesn't this go against the basic principles of the borrow checker? What is going on here?

The one explanation I could imagine is that the actual a closure somehow gets coerced to a function pointer with a static lifetime, and it is just this pointer that is being moved (i.e. copied) into the b closure. However, I have no way to verify this.


Solution

  • Closures will automatically implement Copy if they are able (reference). The closure for a is trivially copyable since it does not capture anything. And when you move something that implements Copy, the original is still usable which is why a can be both moved into b as well as returned.