Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
806 views
in Technique[技术] by (71.8m points)

rust - 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().

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 of the borrowed context, 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();
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...