Why Rust consider this as borrow as mutable reference twice?

This is the source code:

struct A<'a>(&'a mut String);

impl<'a> A<'a> {
    fn get_mut(&mut self) -> Option<&mut String> {
        Some(self.0)
    }
}

fn foo<'a>(a: &'a mut A) -> &'a mut String {
    let k=match a.get_mut() {
        Some(s) => s, //a.get_mut().unwrap(),
        None => a.get_mut().unwrap()
    };
    k // Not return `k` also could pass the compilation
}

The playground link:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f34d177e79b7212642819598a5ec4128
This code cannot be built, this is the detail:

error[E0499]: cannot borrow `*a` as mutable more than once at a time
  --> src/lib.rs:12:17
   |
9  | fn foo<'a>(a: &'a mut A) -> &'a mut String {
   |        -- lifetime `'a` defined here
10 |     let k=match a.get_mut() {
   |                 ----------- first mutable borrow occurs here
11 |         Some(s) => s, //a.get_mut().unwrap(),
12 |         None => a.get_mut().unwrap()
   |                 ^^^^^^^^^^^ second mutable borrow occurs here
13 |     };
14 |     k // Not return `k` also could pass the compilation
   |     - returning this value requires that `*a` is borrowed for `'a`

I tried to drop "first mutable borrow" as follow, but it still couldn't be built, and the error message is the same:

    let k=match a.get_mut() {
        Some(s) => s, //a.get_mut().unwrap(),
        n @ None => {
            drop(n);
            a.get_mut().unwrap()
        }
    };

Finally, I found a compromise workaround:

fn foo<'a>(a: &'a mut A) -> &'a mut String {
    let k=match a.get_mut() {
        Some(_) => a.get_mut().unwrap(),
        None => a.get_mut().unwrap()
    };
    k // Not return `k` also could pass the compilation
}

This time, the compiler was satisfied, but not am I. It added a extra overhead of calling a.get_mut twice just for safety checking. Is this a flaw of borrow checker, or it indeed needs to call a.get_mut twice?

Note: This is not a real case, but a simplified demo extracted from my real project, so please do not tell me to omit the Option checking. XD

Am I missing something? If a.get_mut() returns None on line 10, what makes you think it will return anything other than None on line 12? In this case, the unwrap on line 12 will just panic, so you should be explicit about it:

fn foo<'a>(a: &'a mut A) -> &'a mut String {
    let k=match a.get_mut() {
        Some(s) => s,
        None => unreachable!(),
    };
    k
}

edit:

Or, you know, just unwrap it directly since this does the same thing:

fn foo<'a>(a: &'a mut A) -> &'a mut String {
    a.get_mut().unwrap()
}

Yes you're right, but it is not a real case. Indeed, None arm here is unreachable, but it is just a simplified case. In my real project I won't do that.

Can you describe in more detail why you need to call your a.get_mut() analogue twice? The example used for demonstration does not provide any actionable information.

This is the practical case of my project, but it can only make the problem unclear if I paste the real code.


In this case, I will insert a new json object at None arm and return the new mutable reference.
In this demo, I omitted the extra operation using mutable borrow.

Maybe I'm not good at creating demo though.

It's a flaw in the borrow checker, AKA NLL Problem Case #3. It compiles with Polonius.

2 Likes

You problem lies with the fact that since Some arm keeps the borrowed ownership the other arm keeps it, too.

What happens you use if let ?

Like this:

  if let Some(a) = a.get_mut(path) {
    // use unwrapped a here.
  } else {
    a.get_mut(path2);
  }

In that case both arms are separate thus borrow taken in one shouldn't really affect the other one.

Do you mean this? No, it also cannot be built.

struct A<'a>(&'a mut String);

impl<'a> A<'a> {
    fn get_mut(&mut self) -> Option<&mut String> {
        Some(self.0)
    }
}

fn foo<'a>(a: &'a mut A) -> &'a mut String {
    let k=if let Some(s) = a.get_mut() {
        s
    }else{
        a.get_mut().unwrap()
    };
    k // Not return `k` also could pass the compilation
}

Since you are inserting in one branch, you either need to use polonius (as mentioned earlier) or do the borrow within the branches (as in your solution). This is the same as what you arrived at, but maybe a bit easier to grok:

match iter.next() {
    Some(path) => {
        if obj.get(path).is_some() {
            foo(obj.get_mut(path).unwrap(), iter)
        } else {
            obj.insert(path, JsonValue::Object(Object::new()));
            foo(obj.get_mut(path).unwrap(), iter)
        }
    }
    None => obj,
}

Yes I know, but it's not different than my compromise solution.
Your workaround also needs to judge obj.get_mut is Some or None twice.

It's practically identical. I guess it's a compromise, syntactically. But with compiler optimizations, it really doesn't matter much.

1 Like

You are returning a mutable reference while matching one at the same time.

let n = a.get_mut();
let k =match n{
//......
Some(s)=>s,
None=>n.unwrap(),
};
k

TIL you can use Polonius on nightly.

Well, maybe. But the problem indeed exists, which still needs to be settled. :thinking:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.