将借用变量的向量传递给新的 Struct

Pass vector of borrowed variables to new Struct

为了学习结构、借用和生命周期,我正在组装一个玩具库来处理图形的节点和边。这很有启发性,但是当我最终用 Nodes 实例化一个 Graph 实例时,我被卡住了,这个实例已经被多个 Edges.

借用了

我收到的错误:

error[E0505]: cannot move out of `n0` because it is borrowed
  --> src/lib.rs:94:18
   |
90 |         let e0 = Edge::new(&n0, &n1);
   |                            --- borrow of `n0` occurs here
...
94 |             vec![n0, n1, n2],
   |                  ^^ move out of `n0` occurs here
95 |             vec![e0, e1, e2],
   |                  -- borrow later used here

正在使用的代码:

use std::fmt;
use uuid::Uuid;

#[derive(PartialEq)]
struct Node {
    id: Uuid,
    label: Option<String>,
}

impl fmt::Display for Node {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "<Node {}>", self.id)
    }
}

impl Node {
    fn new() -> Node {
        Node {
            id: Uuid::new_v4(),
            label: None,
        }
    }

    fn new_with_id(id: Uuid) -> Node {
        Node {
            id,
            label: None,
        }
    }
}

struct Edge<'a> {
    nodes: (&'a Node, &'a Node),
}

impl fmt::Display for Edge<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "<Edge ({}, {})>", self.nodes.0, self.nodes.1)
    }
}

impl Edge<'_> {
    fn new<'a>(n0: &'a Node, n1: &'a Node) -> Edge<'a> {
        Edge {
            nodes: (n0, n1)
        }
    }
}

struct Graph<'a> {
    nodes: Vec<Node>,
    edges: Vec<Edge<'a>>,
}

impl Graph<'_> {
    fn new<'a>(nodes: Vec<Node>, edges: Vec<Edge>) -> Graph {
        Graph {
            nodes,
            edges,
        }
    }
}

///////////////////////////////////////////////////////////////////////
// Tests
///////////////////////////////////////////////////////////////////////
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn create_edge() {
        let n0 = Node::new();
        let n1 = Node::new();
        let e0 = Edge::new(&n0, &n1);
        println!("Created node: {}", n0);
        println!("Created node: {}", n1);
        println!("Created edge: {}", e0);
        assert!(e0.nodes.0 == &n0 && e0.nodes.1 == &n1);
    }

    #[test]
    fn create_undirected_graph() {
        let n0 = Node::new();
        let n1 = Node::new();
        let n2 = Node::new();
        let e0 = Edge::new(&n0, &n1);
        let e1 = Edge::new(&n1, &n2);
        let e2 = Edge::new(&n2, &n0);
        let g0 = Graph::new(
            vec![n0, n1, n2],
            vec![e0, e1, e2],
        );
    }
}

感觉我想修改 struct Graph 定义以期望在向量中借用实例,但是 运行 在我朝那个方向前进时出现了一堆编译器错误。

如有任何帮助或指导,我们将不胜感激!

一旦你借用了任何数据结构,你就不能改变它,除非通过那个借用。现在,你的边缘不变地借用你的节点。由于您的节点位于 Vec 中,因此您的边也隐含地借用了整个向量。 (如果他们没有,你可以,比如说,调整向量的大小,这会改变你的节点的位置。)这意味着你不能改变你的节点向量的任何内容。

但是,您正在尝试将节点向量移动到结构内的新内存位置。由于 Vec 不是 Copy,这会使您之前的矢量无效,这显然是在“改变”它。

您可以通过多种方式避免此问题。

借用节点向量

与其将节点移动到 Graph,不如借用它们。

struct Graph<'a> {
    nodes: &'a Vec<Node>,
    edges: Vec<Edge<'a>>,
}

虽然这应该可以解决您当前的编译错误,但生成的结构处理起来不是很愉快,因为它不能单独存在。只要节点向量存在于别处,它就可以存在。

使用引用计数

如果您想避免依赖“外部借用”,您可以回退到良好的旧引用计数。如果您了解 C++,这与共享指针的概念类似。它在标准库中作为 std::rc::Rc 提供。它将一个对象和一个它被引用频率的计数器一起放在堆上,并确保只要存在一个引用就不会释放内存。如果将节点放在这样的抽象之后,则不再需要生命周期(因为在运行时确保了所需的属性)。

在这种情况下,您还需要稍微更改 Edges。

struct Edge {
    nodes: (Rc<Node>, Rc<Node>),
}

struct Graph {
    nodes: Vec<Rc<Node>>,
    edges: Vec<Edge>,
}

通过 ID 或索引引用节点

虽然以前的方法工作得很好,但您通常希望避免额外的堆分配。处理此类问题的标准方法之一是不存储对边缘节点的引用,而是存储它们的索引(或者在您的情况下可能是它们的 UUID)。

自引用结构

您尝试创建的结构是自引用的。这意味着您的字段 edges 想要引用同一结构中另一个字段的数据,即 nodes。虽然在 Rust 中创建这样的结构并非不可能,但要做到正确通常并不容易,而且很容易出错。有一些库执行黑魔法来使这更容易,例如 ouroboros。但是,如果可能的话,我会尽量避免这样的咒语。