Rust borrow checker and early returns
Asked Answered
D

1

8

Rust-lang Playground link

struct Foo {
    val: i32
}

impl Foo {
    pub fn maybe_get(&mut self) -> Option<&mut i32> {
        Some(&mut self.val)
    }
    
    pub fn definitely_get(&mut self) -> &mut i32 {
        { // Add closure to ensure things have a chance to get dropped
            if let Some(val) = self.maybe_get() {
                // Explicit return to avoid potential scope sharing with an else block or a match arms.
                return val;
            }
        }

        // One would think any mutable references would not longer be at play at this point

        &mut self.val   
    }
}

I have some code that's similar but more complicated than what is provided above that I've been fighting with for quite a while. The borrow checker is unhappy with the implementation of definitely_get and has the following error

error[E0499]: cannot borrow `self.val` as mutable more than once at a time
  --> src/main.rs:19:9
   |
10 |     pub fn definitely_get(&mut self) -> &mut i32 {
   |                           - let's call the lifetime of this reference `'1`
11 |         {
12 |             if let Some(val) = self.maybe_get() {
   |                                ---------------- first mutable borrow occurs here
13 |                 return val;
   |                        --- returning this value requires that `*self` is borrowed for `'1`
...
19 |         &mut self.val
   |         ^^^^^^^^^^^^^ second mutable borrow occurs here

It seems unreasonable for there to be no way to implement fallback logic with a mutable reference in Rust so I can't imagine there isn't a way.

Dusky answered 20/1, 2022 at 2:0 Comment(7)
It works with the experimental Polonius borrow checker (try compiling with cargo +nightly rustc -- -Zpolonius).Stgermain
Does this answer your question? Why does rust consider borrows active in other branchesStgermain
I'm not willing to use unstable features for this project and while I appreciate the idea of re-organizing, I can't use Clone or Copy in my real situation because I am explicitly interested in getting the mutable reference back so I can mess with it.Dusky
If you cannot use Polonius (which I don't recommend except for exercising), and you cannot clone/copy, you have no other way but to re-organize your control flow.Stgermain
@ChayimFriedman, could you share a snippet of how I could re-organize to make it compile? I've tried a bunch of different things but can't get past the double borrow issue.Dusky
{ // Add closure This is a scope, not closure, FYI. Your point still stands, though.Rose
I don't really want to post it as an answer since a lot of beginners will misuse it to work around legitimate errors, but you can escape through a pointer to end the borrow. Check out std::ptr::addr_of_mut. You'll need an unsafe block to do this, but it is correct in this case. I have some fairly complicated data structures where a similar problem occurs, unfortunately.Rose
D
0

I've managed to fix this with an unfortunately expensive alternative implementation due to how maybe_get is implemented in my non-trivial example.

impl Foo {
    pub fn has_maybe_val(&self) -> bool {
        // Non-trivial lookup...
        true
    }

    pub fn maybe_get(&mut self) -> Option<&mut i32> {
        // Same non-trivial lookup...
        Some(&mut self.val)
    }
    
    pub fn definitely_get(&mut self) -> &mut i32 {
        if self.has_maybe_val() {
            self.maybe_get().unwrap() // Ouch!
        } else {
            &mut self.val
        } 
    }
}
Dusky answered 20/1, 2022 at 2:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.