简体   繁体   中英

Rust Borrow checker only complains about borrowing as mutable multiple times when a function that returns a reference with the same lifetime assigned

I'm having problem with some Rust code where I'm being allowed to borrow something as mutable more than once on certain conditions (first confusing part), but not others.

I've written the following example to illustrate: ( Playground )

struct NoLifetime {}
struct WithLifetime <'a> {
    pub field: &'a i32
}

fn main() {
    let mut some_val = NoLifetime {};
    borrow_mut_function(&mut some_val);
    borrow_mut_function(&mut some_val); // Borrowing as mutable for the second time.

    let num = 5;
    let mut life_val = WithLifetime { field: &num };
    borrow_lifetime(&mut life_val);
    borrow_lifetime(&mut life_val); // Borrowing as mutable for the second time.

    let num_again = borrow_lifetime(&mut life_val); // Borrow, assign lifetime result
    borrow_lifetime(&mut life_val); // Compiler: cannot borrow `life_val` as mutable more than once
}

fn borrow_mut_function(val_in: &mut NoLifetime) -> String {
    "abc".to_string()
}
fn borrow_lifetime<'a>(val_in: &'a mut WithLifetime) -> &'a i32 {
    val_in.field
}

If you see, I can borrow both some_val , and life_val as mutable more than once. However, after assigning the return value of borrow_lifetime , I can no longer borrow.

My questions are the following:

  1. From 'The Rules' about Borrowing in the Rust Book , I'm supposed to have 'exactly one mutable reference' in scope to the same value. However, in the code above I'm borrowing as mutable every time I call a borrow_ function.
  2. Why is the same type of borrowing not allowed when I have a function that returns something with the same lifetime as the parameter, and I assign that parameter.

Any help would be appreciated. I imagine what is happening here is that I am misunderstanding what 'borrowing as mutable' really means, and when to determine that something is being borrowed as mutable.

Chris already gave the gist of it , but I think it is worth explaining further.

There are 2 ways to transfer ownership in Rust:

  • moving is a permanent transfer
  • borrowing is a temporary transfer, ownership is expected to be returned

Rust, like many other languages, models time passing using a stack of lexical scopes . As a result, for now , a borrow starts where it is created and extend until the end of its scope.


Thus, the questions of when a borrow ends is akin to asking what scope is the borrow created in.

Let's review your example with numbered lines:

fn main() {
    let mut some_val = NoLifetime {};                // 1
    borrow_mut_function(&mut some_val);              // 2
    borrow_mut_function(&mut some_val);              // 3
                                                     // 
    let num = 5;                                     // 4
    let mut life_val = WithLifetime { field: &num }; // 5
    borrow_lifetime(&mut life_val);                  // 6
    borrow_lifetime(&mut life_val);                  // 7
                                                     //
    let num_again = borrow_lifetime(&mut life_val);  // 8
    borrow_lifetime(&mut life_val);                  // 9
}

When a function is called, the argument is borrowed:

  • at least for the duration of the function call
  • up to the moment the result is dropped, if the result shares a lifetime with the argument

So, let's look at this:

  • on line (2) and (3) you call borrow_mut_function which returns a String : the result does not share any lifetime with the argument, so the argument is only borrowed for the lifetime of the function call.

  • on line (6) and (7) you call borrow_lifetime which returns a &'a i32 : the result shares a lifetime with the argument, so the argument is borrowed until the end of the scope of the result... which is immediately since the result is not used.

  • on line (8) you call borrow_lifetime which returns a &'a i32 and you assign the result to num_again : the result shares a lifetime with the argument, so the argument is borrowed until the end of the scope of num_again .

  • on line (9) you call borrow_lifetime however its argument is still borrow by num_again so the call is illegal.

That's it, this is how Rust works today.


In the future, there is a call for non-lexical borrows . That is, the compiler would realize that:

  • num_again is never used
  • num_again does not have a specific destructor (no Drop implementation)

and could therefore decide that its borrow ends sooner than the end of the lexical scope.

This is about the scope of the borrow, and whether you keep the borrow alive. In most of the above calls, some_val is borrowed during the function call, but returned afterwards when the function returns.

In the exception case:

let num_again = borrow_lifetime(&mut life_val); //Borrow, assign lifetime result

You're borrowing life_val during the call to borrow_lifetime , but since the return value has the same lifetime as the parameter ( 'a ), the borrow's scope is extended to include the lifetime of num_again , ie until the end of the function. It would be unsafe to borrow life_val again, since num_again is still a reference into it.

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