简体   繁体   中英

Why does the compiler error complain about multiple mutable references not dangling reference?

I am trying to understand what exactly happens when functions reborrow mutable references.

fn main() {
    let mut a = String::new();
    let mut b = String::new();
    let aa = &mut a;
    let c = my_fun(aa, &mut b);
    let d = &mut a;
    println!("{}", c);
}
                
fn my_fun<'b>(x: &'b mut String, y: &'b mut String) -> &'b mut String { y }

From my understanding the my_fun reborrows aa as &*aa whose scope would be the my_fun . But due to the lifetime bound I created in the function signature the reborrow should live at least as long as &mut b exist. So the println force the reborrow to live until it.

Shouldn't this be throwing an error of use after free because the anonymous reborrow has only scope inside my_fun ? Outside of this function this reference shouldn't be valid.

But the error I get is:

error[E0499]: cannot borrow `a` as mutable more than once at a time
 --> src/main.rs:7:13
  |
5 |     let aa= &mut a;
  |             ------ first mutable borrow occurs here
6 |     let c = my_fun(aa, &mut b);
7 |     let d = &mut a;
  |             ^^^^^^ second mutable borrow occurs here
8 |     println!("{}", c);
  |                    - first borrow later used 

which would have made sense if the mutable reference was merely copied instead of reborrowed inside the function.

From my understanding the my_fun reborrows aa as &*aa whose scope would be the my_fun .

It's not quite that.

Let's backtrack a bit: why reborrowing?

There is a fundamental difference between &T and &mut T : &T is Copy , whereas &mut T is not even Clone . The result is that &mut T can only be moved and therefore a call such as:

let x: &mut T = /*...*/;
func(x);

Would result in x being unusable after the call. The work-around would then be to introduce a temporary:

let x: &mut T = /*...*/;
let tmp = &mut *x;
func(tmp);

This temporary would re-borrow the pointee of x , and be consumed by func .

And... that's re-borrowing ! The compiler has this "temporary" creation built-in purely for ergonomics!

With that in mind, let's go back to:

From my understanding the my_fun reborrows aa as &*aa whose scope would be the my_fun .

Lifetimes are generally more a range than a point , and this is true here.

The lifetime of tmp in my example above is constrained in 2 ways:

  • It cannot be greater than that of x .
  • It cannot be less than that of func .

Which is another way of saying that it can be anything in between those bounds.

I believe you're overthinking "reborrowing" here.

The lifetime requirements you applied say that the thing being referenced by the return value will have at least the lifetime of the things being referenced by the parameters. That's true (and if it weren't provably true, this wouldn't compile). So there is no dangling reference possible.

There isn't a separate "reborrowed" reference. Borrowing is bookkeeping inside the compiler to keep track of lifetimes. There is no let x = &*aa step that actually occurs or is even particularly implied. This isn't like reference counting where memory actually changes at runtime.

Inside of my_fun , y is a reference that's scoped to the function. But the return value is scoped to the caller. (Functions would be impossible to use if this weren't true, having nothing to do with &mut .)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM