Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I not return a mutable reference to an outer variable from a closure?

I was playing around with Rust closures when I hit this interesting scenario:

fn main() {
    let mut y = 10;

    let f = || &mut y;

    f();
}

This gives an error:

error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
 --> src/main.rs:4:16
  |
4 |     let f = || &mut y;
  |                ^^^^^^
  |
note: first, the lifetime cannot outlive the lifetime  as defined on the body at 4:13...
 --> src/main.rs:4:13
  |
4 |     let f = || &mut y;
  |             ^^^^^^^^^
note: ...so that closure can access `y`
 --> src/main.rs:4:16
  |
4 |     let f = || &mut y;
  |                ^^^^^^
note: but, the lifetime must be valid for the call at 6:5...
 --> src/main.rs:6:5
  |
6 |     f();
  |     ^^^
note: ...so type `&mut i32` of expression is valid during the expression
 --> src/main.rs:6:5
  |
6 |     f();
  |     ^^^

Even though the compiler is trying to explain it line by line, I still haven't understood what exactly it is complaining about.

Is it trying to say that the mutable reference cannot outlive the enclosing closure?

The compiler does not complain if I remove the call f().

like image 842
soupybionics Avatar asked Oct 11 '18 04:10

soupybionics


2 Answers

Short version

The closure f stores a mutable reference to y. If it were allowed to return a copy of this reference, you would end up with two simultaneous mutable references to y (one in the closure, one returned), which is forbidden by Rust's memory safety rules.

Long version

The closure can be thought of as

struct __Closure<'a> {
    y: &'a mut i32,
}

Since it contains a mutable reference, the closure is called as FnMut, essentially with the definition

fn call_mut(&mut self, args: ()) -> &'a mut i32 { self.y }

Since we only have a mutable reference to the closure itself, we can't move the field y out, neither are we able to copy it, since mutable references aren't Copy.

We can trick the compiler into accepting the code by forcing the closure to be called as FnOnce instead of FnMut. This code works fine:

fn main() {
    let x = String::new();
    let mut y: u32 = 10;
    let f = || {
        drop(x);
        &mut y
    };
    f();
}

Since we are consuming x inside the scope of the closure and x is not Copy, the compiler detects that the closure can only be FnOnce. Calling an FnOnce closure passes the closure itself by value, so we are allowed to move the mutable reference out.

Another more explicit way to force the closure to be FnOnce is to pass it to a generic function with a trait bound. This code works fine as well:

fn make_fn_once<'a, T, F: FnOnce() -> T>(f: F) -> F {
    f
}

fn main() {
    let mut y: u32 = 10;
    let f = make_fn_once(|| {
        &mut y
    });
    f();
}
like image 90
Sven Marnach Avatar answered Oct 13 '22 20:10

Sven Marnach


There are two main things at play here:

  1. Closures cannot return references to their environment
  2. A mutable reference to a mutable reference can only use the lifetime of the outer reference (unlike with immutable references)

Closures returning references to environment

Closures cannot return any references with the lifetime of self (the closure object). Why is that? Every closure can be called as FnOnce, since that's the super-trait of FnMut which in turn is the super-trait of Fn. FnOnce has this method:

fn call_once(self, args: Args) -> Self::Output;

Note that self is passed by value. So since self is consumed (and now lives within the call_once function`) we cannot return references to it -- that would be equivalent to returning references to a local function variable.

In theory, the call_mut would allow to return references to self (since it receives &mut self). But since call_once, call_mut and call are all implemented with the same body, closures in general cannot return references to self (that is: to their captured environment).

Just to be sure: closures can capture references and return those! And they can capture by reference and return that reference. Those things are something different. It's just about what is stored in the closure type. If there is a reference stored within the type, it can be returned. But we can't return references to anything stored within the closure type.

Nested mutable references

Consider this function (note that the argument type implies 'inner: 'outer; 'outer being shorter than 'inner):

fn foo<'outer, 'inner>(x: &'outer mut &'inner mut i32) -> &'inner mut i32 {
    *x
}

This won't compile. On the first glance, it seems like it should compile, since we're just peeling one layer of references. And it does work for immutable references! But mutable references are different here to preserve soundness.

It's OK to return &'outer mut i32, though. But it's impossible to get a direct reference with the longer (inner) lifetime.

Manually writing the closure

Let's try to hand code the closure you were trying to write:

let mut y = 10;

struct Foo<'a>(&'a mut i32);
impl<'a> Foo<'a> {
    fn call<'s>(&'s mut self) -> &'??? mut i32 { self.0 }
}

let mut f = Foo(&mut y);
f.call();

What lifetime should the returned reference have?

  • It can't be 'a, because we basically have a &'s mut &'a mut i32. And as discussed above, in such a nested mutable reference situation, we can't extract the longer lifetime!
  • But it also can't be 's since that would mean the closure returns something with the lifetime of 'self ("borrowed from self"). And as discussed above, closures can't do that.

So the compiler can't generate the closure impls for us.

like image 44
Lukas Kalbertodt Avatar answered Oct 13 '22 20:10

Lukas Kalbertodt