【问题标题】:Safely traversing a directed acyclic graph安全地遍历有向无环图
【发布时间】:2016-06-30 04:57:34
【问题描述】:

我正在尝试构建和遍历 DAG。似乎有两种可行的方法:将Rc<RefCell<Node>> 用于边缘,或者使用竞技场分配器和一些unsafe 代码。 (See details here.)

我选择前者,但难以将图遍历到其边缘,因为子节点的任何借用都依赖于其父节点的借用:

use std::cell::RefCell;
use std::rc::Rc;

// See: https://aminb.gitbooks.io/rust-for-c/content/graphs/index.html,
//      https://github.com/nrc/r4cppp/blob/master/graphs/src/ref_graph.rs
pub type Link<T> = Rc<RefCell<T>>;

pub struct DagNode {
    /// Each node can have several edges flowing *into* it, i.e. several owners,
    /// hence the use of Rc. RefCell is used so we can have mutability
    /// while building the graph.
    pub edge: Option<Link<DagNode>>,

    // Other data here
}

// Attempt to walk down the DAG until we reach a leaf.
fn walk_to_end(node: &Link<DagNode>) -> &Link<DagNode> {
    let nb = node.borrow();
    match nb.edge {
        Some(ref prev) => walk_to_end(prev),
        // Here be dragons: the borrow relies on all previous borrows,
        // so this fails to compile.
        None => node
    }
}

我可以修改引用计数,即

fn walk_to_end(node: Link<HistoryNode>) -> Link<HistoryNode> {
    let nb = node.borrow();
    match nb.previous {
        Some(ref prev) => walk_to_end(prev.clone()),
        None => node.clone()
    }
}

但是每次遍历一个节点时增加引用计数似乎是相当的 hack。这里的惯用方法是什么?

【问题讨论】:

  • 你读过这个cglab.ca/~abeinges/blah/too-many-lists/book/… 吗?虽然是关于Rc队列,但是真的可以帮到你。
  • ...如果我正确阅读了该部分,那么作者首先遇到了我遇到的同样问题并放弃了。也就是说,“Rc 真的真的让我们失望了。”
  • @MattKline:这实际上是“无论如何,那是我放弃了 Iter 和 IterMut。我们可以做到,但是呃。”,所以 Gankro 认为这是可能的,但可能并不优雅。
  • 是啊,看来Rc&lt;RefCell&gt;没有办法

标签: rust lifetime directed-acyclic-graphs graph-traversal


【解决方案1】:

Rc 在这里并不是真正的问题:如果你摆脱了 RefCells,一切都会编译。实际上,在某些情况下,这可能是一个解决方案:如果您需要改变节点的内容,而不是边缘,您可以更改数据结构,使边缘不在 RefCell 内。

这个论点也不是真正的问题;编译:

fn walk_to_end(node: &Link<DagNode>) -> Link<DagNode> {
    let nb = node.borrow();
    match nb.edge {
        Some(ref prev) => walk_to_end(prev),
        None => node.clone()
    }
}

这里的问题实际上是返回结果。基本上,没有任何方法可以编写您想要的返回值。我的意思是,理论上你可以让你的方法返回一个 Vec&lt;Ref&lt;T&gt;&gt; 的包装器,但这比仅仅增加结果的引用计数要昂贵得多。

更一般地说,Rc&lt;RefCell&lt;T&gt;&gt; 很难使用,因为它是一种复杂的数据结构:您可以安全地同时改变多个节点,并且它会准确跟踪引用每个节点的边数。

请注意,您不必深入研究不安全的代码即可使用竞技场。 https://crates.io/crates/typed-arena 为 arenas 提供了一个安全的 API。我不确定您链接到的示例为什么使用 UnsafeCell;这当然没有必要。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-04
    • 1970-01-01
    相关资源
    最近更新 更多