【问题标题】:Reorganize boxed elements inside struct重新组织结构内的盒装元素
【发布时间】:2017-10-22 22:57:01
【问题描述】:

我正在尝试在 Rust 中实现平衡 (AVL) 版本的二叉搜索树的轮换代码,但在声明要重组的节点的所有权时遇到问题。

我的结构:

struct Tree<T> {
    root: Box<TreeNode<T>>,
    depth: usize,
}

enum TreeNode<T> {
    Empty,
    Node {
        val: T,
        left: Tree<T>,
        right: Tree<T>,
    },
}

我知道我只能使用一种类型,即Options。这似乎更好一些。

当我想实现旋转时:

T1, T2, T3 and T4 are subtrees.
         z                                      y 
        / \                                   /   \
       y   T4      Right Rotate (z)          x      z
      / \          - - - - - - - - ->      /  \    /  \ 
     x   T3                               T1  T2  T3  T4
    / \
  T1   T2

我找不到重新分配节点的方法。我有一个在z 节点(Tree&lt;T&gt; 节点)上调用的rotate(&amp;mut self, ...) 方法,但我需要使用match *self.root {} 将根TreeNode 转换为其Node 版本以获取组件。这行得通,但我不能使用这些提取的值来创建一个新节点。

如果我试试这个:

fn insert(&mut self, ...) {
   ...
   // need to rotate
   rotate(self, ...);
}

fn rotate(&mut ztree, ...) {
    ztree.root = match *ztree.root {
        // just re assign same tree to test...
        TreeNode::Node {val, left, right} =>
                Box::new(TreeNode::Node {val: val, left: left, right: right}),
        _ => panic!("meh"),
    } ...

我收到此错误。

    |
171 |             ztree.root = match *ztree.root {
    |                                 ^^^^^ cannot move out of borrowed content
172 |                 TreeNode::Node {val, left, right} =>
    |                                 ---  ----  ----- ...and here (use `ref right` or `ref mut right`)
    |                                 |    |
    |                                 |    ...and here (use `ref left` or `ref mut left`)
    |                                 hint: to prevent move, use `ref val` or `ref mut val`

我知道我不喜欢获得盒装 TreeNode 的所有权,但我不知道如何告诉 Rust 我将分配一个新的盒装 TreeNode 和旧的 @987654337 @ 可以在本地声明。

如果我尝试 self.root = Box::new(TreeNode::Empty) 效果很好,因为它知道我正在将 self.root 重新分配给一个新盒子,并且应该释放前一个盒子和引用的堆结构。

【问题讨论】:

  • 看看这段代码:Rust playground link。也许您会发现它对您的情况很有用。
  • 我明白了,@red75prime,您正在使用 Option 并采取分离值。太完美了,我完全忽略了这一点。我认为那会奏效!我的分支是不同的:我没有左/右选项,而是空节点和“有价值”节点。但是我仍然可以使用 Option 来获取值并将其放置在其他地方。非常感谢,考虑创建一个答案,我会完全接受。
  • 正如stackoverflow.com/questions/16504643/… 中解释的那样,Option 不会为指针增加额外的大小,因此 Option> 与 Box 一样小。根本不是多余的东西。很高兴知道!这样我就可以在需要“典型”指针时使用 Option> ,并且可以获取该值并移动它。
  • 另外,在我的代码中,我可以添加一个类似take 的方法来返回 val、left、right 值并将自身转换为 None。那也行。我想我会实现它只是为了确认......然后采用标准的 Option 方式。
  • 请务必注意,Option 并没有什么神奇之处。不向指针添加额外大小(AFAIK)的优化也适用于等效布局的其他枚举。同样,take 方法可以是 implemented 使用安全代码。

标签: data-structures struct tree rust


【解决方案1】:

假设 Rust 确实 信任您替换 ztree.root 的值。然后你可以写

fn rotate(&mut ztree, ...) {
    let locally_owned = ztree.root (and I promise to give it back);
    // Now, ztree.root is in some undefined state. 
    // Thats OK though, because I promise to fix it before anyone looks!

    let local_new_value = match locally_owned {
        // just re assign same tree to test...
        TreeNode::Node {val, left, right} =>
                Box::new(TreeNode::Node {val: val, left: left, right: right}),
        // Looks safe-ish, because the whole program will crash, 
        // so the programmer might expect that no one
        // will see the undefined value of ztree.root
        // (in fact, there would be problems when the destructor
        //  of ztree.root is called in the panic)
        _ => panic!("meh"), 
    }
    // FIXED IT! Put a value back into ztree.root
    ztree.root = local_new_value;
}

看起来还不错。然而,想象一下如果你用一些 return 语句替换了panic("meh")。然后你可以有这样的代码:

ztree.root = Box::new(TreeNode::Empty);
// Returns without replacing the value of ztree.root
// because ztree.root is Empty 
rotate(&mut ztree); 
// Now ztree.root is in a undefined state
rotate(&mut ztree); // So something bad happens here

基本上,编译器必须说服自己,您不仅打算替换 ztree.root 的值,而且没有代码路径会导致值不被替换。这太复杂了,因此,没有办法告诉编译器让你做你想做的事。

相反,您可以通过重述问题来解决问题。无需尝试计算新值来替换旧值,您实际上只需要更改当前值,而无需替换它。一种方法是像这样使用std::mem::swap (Playground):

fn rotate<T>(ztree: &mut Tree<T>) {
    let ref mut root : TreeNode<T> = *ztree.root;

    match root {
        &mut TreeNode::Node {ref mut left, ref mut right, ..} => {
            std::mem::swap(left, right);
        },
        _ => unimplemented!(),
    }
}

如果您想知道为什么 let ref mut root: TreeNode&lt;T&gt; = *ztree.root; 有效但 match *ztree.root {...} 无效,我不太确定,但它可能与 this issue 有关。

【讨论】:

  • 知道了!我明白我做错了什么......我不知道如何协调匹配/借用节点以读取其值......与稍后替换它的事实。问题下方的 cmets 提示我:我应该使用 Option&lt;T&gt;take() 它的值(所以它是我的,Option 为空),或者,出于娱乐和学习的目的,我可以为我的树节点结构。它需要使用mem::replace,这正是您建议的swap,但返回值。我不知道这个功能,也没有意识到它使用起来非常安全可靠。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-19
  • 1970-01-01
  • 1970-01-01
  • 2011-02-28
  • 2013-08-05
  • 1970-01-01
相关资源
最近更新 更多