I can't understand the borrowing system in rust

l can't understand the borrowing system in rust. Can anyone help me understand this
My code:

let mut current_val:&mut Value = &mut *logs;
for key in &group_path {
            let mut temp: Option<&mut serde_json::Value> = None; // Explicit type might help clarity.

            // Check if the key exists and if not, insert a new object.
            if !current_val.as_object_mut().unwrap().contains_key(key) {
                current_val.as_object_mut().unwrap().insert(key.clone(), 
                serde_json::Value::Object(Default::default()));
            }
            temp = current_val.get_mut(key);

            // Update `current_val` for the next iteration
            if let Some(t) = temp {
                current_val = t;
            } else {
                break; // or continue, depending on your logic
            }
}
if let Some(arr) = current_val.as_array_mut() {
            arr.push(serde_json::to_value(log).unwrap());
} else {
            println!("Not array");
}

error:

error[E0499]: cannot borrow `*current_val` as mutable more than once at a time
  --> src/logger.rs:92:28
   |
82 |             temp = current_val.get_mut(key);
   |                    ----------- first mutable borrow occurs here
...
92 |         if let Some(arr) = current_val.as_array_mut() {
   |                            ^^^^^^^^^^^
   |                            |
   |                            second mutable borrow occurs here
   |                            first borrow later used here

In theory, temp should have been removed and there should have been no problem with borrowing

You might run into one of the currently not-so-perfect corner cases - see polonius-the-crab for details.

I've just started learning rust, and there are so many problems. it seems like a simple task. What do you recommend to read to better understand how to do it?
Is there any way to do this without using "polonius-the-crab"?

Here's a reproduction (I assume) in case someone wants to play with it.

(You'll get better help in general if you supply a reproduction.)

1 Like

Here's a workaround for this case that doesn't use the polonius-the-crab.

It's not related to Polonius. This kind of code will likely never be accepted.

Your code can be simplified, removing irrelevant code and changing a loop into a single iteration:

let mut current_val: &mut Value = &mut *logs;
let key = &group_path[0];
if let Some(t) = current_val.get_mut(key) {
    current_val = t;
}
if let Some(arr) = current_val.as_array_mut() {
    arr.push(serde_json::to_value(log).unwrap());
} else {
    println!("Not array");
}

Hopefully the issue is clear now. Depending on whether the key exists in the map, current_val can be set either to a = &mut * logs or b = &mut (&mut *logs)[key]. The latter is a borrow of the former. In the current_val.as_array_mut() expression, you borrow mutably the variable which can be either of those values, thus both borrows a and b must exist simultaneously. But this means that logs is borrowed mutably twice, which is what the borrow checker complains about. actually it complains about a slightly different but essentially the same issue: instead of double-borrowing logs, it fails on the double-borrow of current_val itself, but the reason is the same --- it may be reborrowed in the branch, and both direct borrow and double borrow are thus possible in current_val.as_array_mut() call.

1 Like

It probably will be accepted under Polonius.[1] It requires flow sensitive lifetimes. It's something like...

let mut cv: &mut _ = ...; // &'c mut _
for ... {
    let temp: Option<&mut _> = cv.st(); // &'t mut _ where 'c: 't
    if let Some(inner) = temp {
        cv = inner; // 't: 'c
    } else {
        break;
    }
}

cv; // use 'c

If 't: 'c is not required in the break branch, there is no conflict.

I think it's this issue which is also discussed in this blog post (in section "Refining constraint propagation with liveness") about Polonius.


  1. Current Polonius accepts it, but I recognize that's no guarantee on its own. ↩ī¸Ž

4 Likes

That's actually quite interesting that even something like this (=example from the blog) does currently not compile. :hushed:

EDIT:

You can make it even simpler.

This works for example:

	struct Thing;

	impl Thing {
		fn maybe_next(&mut self) -> Option<&mut Self> {
			Some(self)
		}
	}

	let mut temp = &mut Thing;
          
	temp = temp.maybe_next().unwrap();

	if let Some(x) = temp.maybe_next() {
	    temp = x;
	}

This doesn't:

	struct Thing;

	impl Thing {
		fn maybe_next(&mut self) -> Option<&mut Self> {
			Some(self)
		}
	}

	let mut temp = &mut Thing;
          
	if let Some(x) = temp.maybe_next() {
	    temp = x;
	}

	if let Some(x) = temp.maybe_next() {
	    temp = x;
	}

I try to describe here the the problem in simple words: The borrow checker currently behaves here as it does, because at if let Some(x) = temp.maybe_next() a borrow with the lifetime 'a (=same lifetime as temp) is created but it doesn't consider to release the borrow for everything that happens afterwards because it could also be the case that the inner assignment won't happen if the outer condition is wrong. Is that about right?

Yes, that's how I understand it.

1 Like

Hi. Maybe I'm not helpful, but I think it should be mentioned that if you started to learn rust you should check rust book.

I think the discussion in this thread is about borrow checking details where the book can't help anymore, unfortunately :wink:

2 Likes

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.