借用循环内使用的可变成员

时间:2019-09-09 10:53:44

标签: rust borrow-checker

我要解决的问题是:

  

给出递归嵌套的数据结构,例如。一个JSON树,以及指向其中的元素(可能不存在)的路径,返回该元素的可变引用,该引用最接近给定路径。

示例:如果我们有格式为{ a: { b: { c: "foo" } } }的JSON文档和路径为a.b.d的路径,我们希望有一个可变的指向存储在键“ b”下的值的指针。

这是一个代码段,到目前为止,我已经了解了:

use std::collections::HashMap;

enum Json {
    Number(i64),
    Bool(bool),
    String(String),
    Array(Vec<Json>),
    Object(HashMap<String, Json>)
}

struct Pointer<'a, 'b> {
    value: &'a mut Json,
    path: Vec<&'b str>,
    position: usize
}

/// Return a mutable pointer to JSON element having shared 
/// the nearest common path with provided JSON. 
fn nearest_mut<'a,'b>(obj: &'a mut Json, path: Vec<&'b str>) -> Pointer<'a,'b> {
    let mut i = 0;
    let mut current = obj;
    for &key in path.iter() {
        match current {
            Json::Array(array) => {
                match key.parse::<usize>() {
                    Ok(index) => {
                        match array.get_mut(index) {
                            Some(inner) => current = inner,
                            None => break,
                        }
                    },
                    _ => break,
                }
            } ,
            Json::Object(map) => {
                match map.get_mut(key) {
                    Some(inner) => current = inner,
                    None => break
                }
            },
            _ => break,
        };
        i += 1;
    }
    Pointer { path, position: i, value: current }
}

问题在于,这不会通过Rust的借位检查器传递,因为在构造指针方法时,两次将current作为可变引用来借用一次,一次在match语句内,一次在函数末尾。 / p>

我尝试了不同的方法,但没有弄清楚如何实现目标(也许走不安全的道路)。

1 个答案:

答案 0 :(得分:1)

我完全误解了您的问题,并向您道歉。

您不能一pass而就-您将需要执行只读操作以找到最近的路径(或精确路径),然后进行读写操作以实际提取参考或闭包形式的mutator函数。

我为您implemented进行了两次通过。请注意,它仍然表现出色:

fn nearest_mut<'a, 'b>(obj: &'a mut Json, path: Vec<&'b str>) -> Pointer<'a, 'b> {
    let valid_path = nearest_path(obj, path);
    exact_mut(obj, valid_path).unwrap()
}
fn exact_mut<'a, 'b>(obj: &'a mut Json, path: Vec<&'b str>) -> Option<Pointer<'a, 'b>> {
    let mut i = 0;
    let mut target = obj;
    for token in path.iter() {
        i += 1;
        // borrow checker gets confused about `target` being mutably borrowed too many times because of the loop
        // this once-per-loop binding makes the scope clearer and circumvents the error
        let target_once = target;
        let target_opt = match *target_once {
            Json::Object(ref mut map) => map.get_mut(*token),
            Json::Array(ref mut list) => match token.parse::<usize>() {
                Ok(t) => list.get_mut(t),
                Err(_) => None,
            },
            _ => None,
        };
        if let Some(t) = target_opt {
            target = t;
        } else {
            return None;
        }
    }
    Some(Pointer {
        path,
        position: i,
        value: target,
    })
}
/// Return a mutable pointer to JSON element having shared
/// the nearest common path with provided JSON.
fn nearest_path<'a, 'b>(obj: &'a Json, path: Vec<&'b str>) -> Vec<&'b str> {
    let mut i = 0;
    let mut target = obj;
    let mut valid_paths = vec![];
    for token in path.iter() {
        // borrow checker gets confused about `target` being mutably borrowed too many times because of the loop
        // this once-per-loop binding makes the scope clearer and circumvents the error
        let target_opt = match *target {
            Json::Object(ref map) => map.get(*token),
            Json::Array(ref list) => match token.parse::<usize>() {
                Ok(t) => list.get(t),
                Err(_) => None,
            },
            _ => None,
        };
        if let Some(t) = target_opt {
            target = t;
            valid_paths.push(*token)
        } else {
            return valid_paths;
        }
    }
    return valid_paths
}

原理很简单-为了获得最近的有效路径(或精确路径),我重用了我在最初的问题中写的方法。

从那里,我直接将其输入到原始答案中的函数中,并且由于我确定路径是有效的(来自先前的函数调用),因此我可以安全地unwrap():-)