如何在& mut ref

时间:2015-11-14 06:09:09

标签: rust

有时我会遇到一个问题,由于用户看不到的实现细节,我需要“销毁”&mut并将其替换为内存。这通常最终发生在递归方法或递归结构上的IntoIterator实现中。它通常遵循以下形式:

fn create_something(self);

pub fn do_something(&mut self) {
    // What you want to do
    *self = self.create_something();
}

我在当前项目中碰巧遇到的一个例子是我编写的KD树,当我“删除”一个节点,而不是重新安排孩子的逻辑时,我只是解构我需要删除的节点并从子树中的值重建它:

// Some recursive checks to identify is this is our node above this

if let Node{point, left, right} = mem::replace(self, Sentinel) {
    let points = left.into_iter().chain(right.into_iter()).collect();
    (*self) = KDNode::new(points);

    Some(point)
} else {
    None
}

另一个更深入的例子是这个KDTree的IntoIterator,它必须将curr值移出迭代器,测试它,然后替换它:

// temporarily swap self.curr with a dummy value so we can
// move out of it
let tmp = mem::replace(&mut self.curr, (Sentinel,Left));

match tmp {
    // If the next node is a Sentinel, that means the
    // "real" next node was either the parent, or we're done
    (Sentinel,_) => {
        if self.stack.is_empty() {
            None
        } else {
            self.curr = self.stack.pop().expect("Could not pop iterator parent stack");
            self.next()
        }
    }
    // If the next node is to yield the current node,
    // then the next node is it's right child's leftmost
    // descendent. We only "load" the right child, and lazily
    // evaluate to its left child next iteration.
    (Node{box right,point,..},Me) => {
        self.curr = (right,Left);

        Some(point)
    },
    // Left is an instruction to lazily find this node's left-most
    // non-sentinel child, so we recurse down, pushing the parents on the
    // stack as we go, and then say that our next node is our right child.
    // If this child doesn't exist, then it will be taken care of by the Sentinel
    // case next call.
    (curr @ Node{..},Left) => {
        let mut curr = curr;
        let mut left = get_left(&mut curr);

        while !left.is_sentinel() {
            self.stack.push((curr,Me));
            curr = left;
            left = get_left(&mut curr);
        }

        let (right,point) = get_right_point(curr);
        self.curr = (right, Left);
        Some(point)
    }

正如您所看到的,我当前的方法是仅使用带有虚拟值的mem::replace,然后稍后覆盖虚拟值。但是,由于以下几个原因,我不喜欢这个:

  • 在某些情况下,没有合适的虚拟值。如果没有公共/简单的方法为一个或多个结构成员构造“零值”(例如,如果结构持有MutexGuard会怎样?),则尤其如此。如果你需要虚拟替换的成员在另一个模块(或crate)中,你可能受到其构造的困难约束的约束,这些约束在尝试构建虚拟类型时是不可取的。
  • 结构可能相当大,在这种情况下,进行多于必要的移动可能是不可取的(实际上,这不太可能是一个大问题,不可否认)。
  • 它只是“感觉”不洁,因为“移动”在技术上更像是一种“更新”。事实上,最简单的例子可能是*self = self.next.do_something(),它仍然会有问题。

在某些情况下,例如我展示的第一个remove代码段,你或许可以更清晰地将其表示为fn do_something(self) -> Self,但在其他情况下,例如IntoIterator示例,则无法完成因为你受到特质定义的约束。

有没有更好,更清洁的方法来进行这种就地更新?

1 个答案:

答案 0 :(得分:1)

在任何情况下,我们都需要分配,mem::replacemem::swap或类似的东西。因为给定对象的&mut引用,所以无法移动此对象(或其任何字段)而无需用有效的东西替换它的内存区域,只要Rust禁止对未初始化记忆的引用。

对于替换的虚拟值,您可以使用某种包装类型自己为任何类型创建它们。例如,我经常使用Option来实现此目的,其中Some(T)T类型的值,None充当虚拟。这就是我的意思:

struct Tree<T>(Option<Node<T>>);
enum Node<T> {
    Leaf(T),
    Children(Vec<Tree<T>>),
}

impl<T> Tree<T> where T: PartialEq {
    fn remove(&mut self, value: &T) {
        match self.0.take() {
            Some(Node::Leaf(ref leaf_value)) if leaf_value == value =>
                (),
            node @ Some(Node::Leaf(..)) =>
                *self = Tree(node),
            Some(Node::Children(node_children)) => {
                let children: Vec<_> =
                    node_children
                        .into_iter()
                        .filter_map(|mut tree| { tree.remove(value); tree.0 })
                        .map(|node| Tree(Some(node)))
                        .collect();
                if !children.is_empty() {
                    *self = Tree(Some(Node::Children(children)));
                }
            },
            None =>
                panic!("something went wrong"),
        }
    }
}

playground link