如何在两个地方存储结构?

时间:2017-12-13 02:08:33

标签: rust borrow-checker

我正在做Rust第7天的代码。我必须像这样解析一棵树:

a(10)
c(5) -> a, b
b(20)

这表示c是以ab作为其子女的根。

我通过解析每一行,创建一个对象,并按名称将它存储在一个哈希中来处理这个问题。如果它稍后显示为子项,如a,我可以使用该哈希查找对象并将其作为子项应用。如果它在定义之前显示为子项,如b,我可以创建一个部分版本并通过哈希更新它。以上就是这样的:

let mut np = NodeParser{
    map: HashMap::new(),
    root: None,
};

{
    // This would be the result of parsing "a(10)".
    {
        let a = Node{
            name: "a".to_string(),
            weight: Some(10),
            children: None
        };
        np.map.insert( a.name.clone(), a );
    }

    // This is the result of parsing "c(5) -> a, b".
    // Note that it creates 'b' with incomplete data.
    {
        let b = Node{
            name: "b".to_string(),
            weight: None,
            children: None
        };
        np.map.insert("b".to_string(), b);

        let c = Node{
            name: "c".to_string(),
            weight: Some(5),
            children: Some(vec![
                *np.map.get("a").unwrap(),
//              ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content

                *np.map.get("b").unwrap()
//              ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content
            ])
        };
        np.map.insert( c.name.clone(), c );
    }

    // Parsing "b(20)", it's already seen b, so it updates it.
    // This also updates the entry in c.children. It avoids
    // having to search all nodes for any with b as a child.
    {
        let mut b = np.map.get_mut( "b" ).unwrap();
        b.weight = Some(20);
    }
}

我可能想查找一个节点并查看其子节点。

// And if I wanted to look at the children of c...
let node = np.map.get("c").unwrap();
for child in node.children.unwrap() {
//           ^^^^ cannot move out of borrowed content
    println!("{:?}", child);
}

Rust不喜欢这个。它并不是说NodeParser.mapNode.children拥有一个节点。

error[E0507]: cannot move out of borrowed content
  --> /Users/schwern/tmp/test.rs:46:21
   |
46 |                     *np.map.get("a").unwrap(),
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content

error[E0507]: cannot move out of borrowed content
  --> /Users/schwern/tmp/test.rs:49:21
   |
49 |                     *np.map.get("b").unwrap()
   |                     ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot move out of borrowed content

它并不是说for循环试图借用节点进行迭代,因为我已经从拥有它的NodeParser借用了该节点。

error[E0507]: cannot move out of borrowed content
  --> /Users/schwern/tmp/test.rs:68:18
   |
68 |     for child in node.children.unwrap() {
   |                  ^^^^ cannot move out of borrowed content

我想我明白自己做错了什么,但我不确定如何做对。

我应该如何构建这个以使借款人高兴?由于必须链接NodeParser.mapNode.children的方式,因此无法复制。

Here is the code to test with。在实际代码中,NodeNodeParser都有实现和方法。

1 个答案:

答案 0 :(得分:4)

一个选项是不安全的代码...但是如果你使用代码的出现来学习惯用的Rust而不仅仅是放弃它试图给你的所有安全性,我建议避免使用它。

另一种选择是引用Node实例的计数,以便借用检查器很高兴并且编译器知道如何清理。 std::rc::Rc类型为您执行此操作...基本上every call to clone() just increments a reference count并返回一个新的Rc实例。然后每次删除一个对象时,Drop实现只会减少引用计数。

至于迭代.. for x in yfor x in y.into_iter()的语法糖。这是为了将children的内容移出nodethe IntoIterator trait中的通知,into_iter(self)取得self的所有权)。要解决此问题,您可以使用for x in &y在迭代时请求引用。这基本上变成了for x in y.iter(),它不会移动内容。

Here are these suggestions in action

use std::collections::HashMap;
use std::rc::Rc;

struct NodeParser {
    map: HashMap<String, Rc<Node>>,
    root: Option<Node>,
}

#[derive(Debug)]
struct Node {
    name: String,
    children: Option<Vec<Rc<Node>>>,
}

fn main() {
    let mut np = NodeParser{
        map: HashMap::new(),
        root: None,
    };

    let a = Rc::new(Node{ name: "a".to_string(), children: None });
    np.map.insert( a.name.clone(), a.clone() );

    let b = Rc::new(Node{ name: "b".to_string(), children: None });
    np.map.insert( b.name.clone(), b.clone() );

    let c = Rc::new(Node{
        name: "c".to_string(),
        children: Some(vec![a, b])
    });
    np.map.insert( c.name.clone(), c.clone() );

    let node = np.map.get("c").unwrap();
    for child in &node.children {
        println!("{:?}", child);
    }
}

编辑:我将在此扩展我的评论。 You can use lifetimes here too if you want,但我担心终身解决方案会对MCVE产生影响,并且一旦应用于实际问题 OP就不会起作用(不仅仅是这个问题......其他问题) (实际上)。 Rust中的生命周期很棘手,像重新排序变量的实例化以允许终身解决方案可能会让人失望。我担心他们会遇到终身问题,因此即使它适用于MCVE,答案也不适合他们的实际情况。也许我会过度思考..