Mutable borrow is used, but I can't understand why

I was trying to perform some kind of operation on serde_json Values - insert a new value into the existing hierarchy, to be precise, - and run into some strange borrow-checker error. Here's the simplified reproduction:

#![allow(unused_assignments)]

enum Recursive {
    Data(Option<Vec<Recursive>>),
}
use Recursive::Data;

fn descend(mut rec: Recursive) {
    let mut rec = &mut rec;

    match rec {
        Data(Some(v_rec)) => {
            if let Some(next) = v_rec.get_mut(0) {
                rec = next;
                return;
            }
            let _ = v_rec.len();
        }
        _ => panic!(),
    }
}

(Playground)

Errors:

error[E0502]: cannot borrow `*v_rec` as immutable because it is also borrowed as mutable
  --> src/lib.rs:17:21
   |
13 |             if let Some(next) = v_rec.get_mut(0) {
   |                                 ----- mutable borrow occurs here
...
17 |             let _ = v_rec.len();
   |                     ^^^^^
   |                     |
   |                     immutable borrow occurs here
   |                     mutable borrow later used here

But I don't see how exactly it is used. Can someone check this?

It's because rec is a single variable, so it has a single lifetime. There are paths that go through your call to len while rec is active, and storing next in rec requires that next is valid everywhere that rec is.

1 Like

The current borrow checker has trouble reasoning about borrows that are held when a function returns early. This is fixed in the next-generation borrow checker, Polonius.

The nightly version of rustc will compile the above code if you pass the experimental -Zpolonius option.

2 Likes

I see, thanks. I'd like to stick to stable for now, so it seems I have to rethink this algorithm.

If you can share a more complete code sample, we might be able to jelp find workarounds.

Well, the more general task here is to "flatten" JSON into the BTreeMap (to be processed by other part of the application) and then "de-flatten" it back after processing. BTreeMap has the following as its keys:

#[derive(Clone, PartialOrd, PartialEq, Ord, Eq)]
enum JsonPathPart {
    Index(usize),
    Key(String),
}
type JsonPath = Vec<JsonPathPart>;

I can create the map, the problem is with getting back. I'm trying to do it by iterating over map and inserting every value into an initially empty Value, where the iteration looks currently like this (extracted into the function, so that it can potentially be compiled):

// root is created based on the map too, I omitted this here for simplicity
fn make_value(map: std::collections::BTreeMap<JsonPath, Value>, mut root: Value) -> Value {
    for (path, mut value) in map {
        // building a Values chain from path
        for part in path.clone().into_iter().rev() {
            match part {
                JsonPathPart::Index(_) => value = Value::Array(vec![value]),
                JsonPathPart::Key(key) => value = Value::Object(once((key, value)).collect()),
            }
        }
        let mut dest = &mut root;
        let mut src = &mut value;
        // each iteration of this loop corresponds to the code from the first post
        for part in path {
            match (dest, part, src) {
                (Value::Array(arr), JsonPathPart::Index(index), Value::Array(new)) => {
                    let existing = arr.get_mut(index);
                    if let Some(next) = existing {
                        dest = next;
                        src = &mut new[0];
                        continue;
                    }
                    debug_assert!(arr.len() == index);
                    debug_assert!(new.len() == 1);
                    arr.push(new.pop().unwrap());
                    break;
                }
                // this branch is yet to be written, but the idea was similar
                (Value::Object(obj), JsonPathPart::Key(ref key), Value::Object(new)) => todo!(),
                _ => panic!("JSON was modified incompatibly"),
            }
        }
    };
    root
}

Playground
Not sure if I can simplify the code without losing the context, sorry. Maybe this approach is flawed at all.

I've managed to do it by splitting the insertion in two phases: first find the place to insert by reusing the same reference and collecting the path, then query the collected path directly with pointer_mut. Here's how it is done now:

for (path, mut value) in map {
    // `root` object is created beforehand
    let mut dest = &mut root;
    let mut insertion_path = String::new();
    let mut inserted_key = None;
    for part in path {
        match part {
            JsonPathPart::Index(index) => {
                // descend into the current value - this is OK, since this reference
                // isn't stored in any other variable, only in `value` itself
                value = value.as_array_mut().unwrap().remove(0);
                match dest.get_mut(index) {
                    Some(item) => {
                        // descend into the "root" object -
                        // this is also OK, since dest will be dropped after loop
                        dest = item;
                        insertion_path.push_str(&format!("/{}", index));
                    }
                    None => {
                        inserted_key = Some(part);
                        break;
                    }
                }
            }
            JsonPathPart::Key(ref key) => {
                // the same logic
                value = value.as_object_mut().unwrap().remove(key).unwrap();
                match dest.get_mut(key) {
                    Some(item) => {
                        dest = item;
                        insertion_path.push_str(&format!(
                            "/{}",
                            key.replace('~', "~0").replace('/', "~1")
                        ));
                    }
                    None => {
                        inserted_key = Some(part);
                        break;
                    }
                }
            }
        }
    }
    // now, we hold to references into the `root`, so we can query it directly
    match (inserted_key, root.pointer_mut(&insertion_path)) {
        (Some(JsonPathPart::Index(index)), Some(Value::Array(arr))) => {
            // Due to the BTreeMap ordering guarantees, this index is definitely out of range.
            debug_assert!(arr.len() <= index);
            arr.resize_with(index + 1, Default::default);
            arr[index] = value;
        }
        (Some(JsonPathPart::Key(ref key)), Some(Value::Object(obj))) => {
            obj.insert(key.clone(), value);
        }
        (key, value) => panic!(
            "JSON was modified incompatibly: key {:?} was going to be inserted into {:?}",
            key, value
        ),
    }
}

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.