0
votes

I need a function similar to assocPath, I want to confirm whether a certain child node exists, if it exists, obtain its mutable reference, if it does not exist, create and obtain that.

I encountered some difficulties, my parent node is a mutable reference, and the child node is also a mutable reference, so the reference of the parent node should be released, but the compiler some how tells me that "cannot assign twice to immutable variable", this confuses me.

The following is my attemption, how can I solve this?

use std::collections::HashMap;

pub enum JsonValue {
    Null,
    Bool(bool),
    Number(f32),
    String(String),
    Array(Vec<JsonValue>),
    Object(HashMap<String, JsonValue>),
}

impl JsonValue {
    fn new() -> JsonValue { JsonValue::Object(HashMap::new()) }
    fn check_key_path(mut self, path: &str) -> &JsonValue {
        let ref mut node = self;
        for k in path.split(".") {
            match node {
                JsonValue::Object(dict) => {
                    node = match dict.get(k) {
                        Some(ref mut s) => s,
                        None => &mut dict.insert(k.to_string(), JsonValue::new()).unwrap()
                    }
                }
                _ => panic!("NotObject")
            }
        }
        node
    }
}

fn main() {
    let mut v = JsonValue::new();
    v.check_key_path("some.node.path");
}
1

1 Answers

2
votes

There are a few issues here:

  • check_key_path takes self by value, which means self is consumed by the method. Once self is consumed, the &JsonValue you want to return from the method will be dropped, so the reference isn't valid.
  • let ref mut declares an immutable binding to a mutable reference, meaning you can change the referenced data but not what data the binding references. This is the error you see when attempting to reassign node.
  • You borrow dict as immutable when calling dict.get(k), then try to dict.insert() (which mutably borrows dict).

The standard library provides a type Entry<K, V> that helps with this sort of thing: you can get a value from a HashMap or create it if it doesn't exist with Entry::or_insert.

Here's a playground link for the updated code, which will look like this:

// takes &mut self instead of mut self
fn check_key_path(&mut self, path: &str) -> &JsonValue {
    // declare node as a mutable binding with value &mut self
    let mut node = self;
    for k in path.split(".") {
        match node {
            JsonValue::Object(dict) => {
                // get or create value with key k
                node = dict.entry(k.to_string()).or_insert(JsonValue::new());
            }
            _ => panic!("NotObject")
        }
    }
    node
}