有时我会遇到一个问题,由于实现细节应该对用户不可见,我需要“销毁” a&mut并在内存中替换它。这通常最终发生在递归方法或递归结构的 IntoIterator 实现中。它通常遵循以下形式:
fn create_something(self);
pub fn do_something(&mut self) {
// What you want to do
*self = self.create_something();
}
Run Code Online (Sandbox Code Playgroud)
我在当前项目中碰巧遇到的一个例子是在我编写的 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
}
Run Code Online (Sandbox Code Playgroud)
另一个更深入的例子是这个 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)
}
Run Code Online (Sandbox Code Playgroud)
如您所见,我目前的方法是仅使用mem::replace虚拟值,然后稍后覆盖虚拟值。但是,我不喜欢这样有几个原因:
*self = self.next.do_something()仍然会有问题的东西。在某些情况下,例如remove我展示的第一个片段,您也许可以更清楚地将其表示为 a fn do_something(self) -> Self,但在其他情况下,例如 IntoIterator 示例,这无法完成,因为您受到 trait 定义的约束。
有没有更好、更干净的方法来进行这种就地更新?
无论如何,我们都需要赋值、、、mem::replace或mem::swap类似的东西。因为给定&mut一个对象的引用,只要 Rust 禁止引用未初始化的内存,就无法在不用有效内容替换其内存区域的情况下将该对象(或其任何字段)移出。
至于用于替换的虚拟值,您始终可以通过使用某种包装类型为任何类型自行制作它们。例如,我经常Option为此目的使用Some(T)type 的值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"),
}
}
}
Run Code Online (Sandbox Code Playgroud)