如何使用和替换 &mut ref 中的值 [英] How to consume and replace a value in an &mut ref

查看:40
本文介绍了如何使用和替换 &mut ref 中的值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Sometimes I run into a problem where, due to implementation details that should be invisible to the user, I need to "destroy" a &mut and replace it in-memory. This typically ends up happening in recursive methods or IntoIterator implementations on recursive structures. It typically follows the form of:

fn create_something(self);

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

我在当前项目中碰巧遇到的一个示例是在我编写的 KD 树中,当我删除"一个节点时,我没有执行重新排列子节点的逻辑,而是解构我需要删除的节点并从其子树中的值重建它:

One example that I happened to have in my current project is in a KD Tree I've written, when I "remove" a node, instead of doing logic to rearrange the children, I just destructure the node I need to remove and rebuild it from the values in its subtrees:

// 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 值移出迭代器,测试它,然后替换它:

Another more in-depth example is the IntoIterator for this KDTree, which has to move a curr value out of the iterator, test it, and then replace it:

// 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 与虚拟值一起使用,然后稍后覆盖虚拟值.但是,我不喜欢这个有几个原因:

As you can see, my current method is to just use mem::replace with a dummy value, and then just overwrite the dummy value later. However, I don't like this for several reasons:

  • 在某些情况下,没有合适的虚拟值.如果没有为一个或多个结构成员构造零值"的公共/简单方法(例如,如果结构持有 MutexGuard 会怎样?),这一点尤其正确.如果您需要虚拟替换的成员在另一个模块(或 crate)中,您可能会受到其构造的困难限制,而这些限制在尝试构建虚拟类型时是不受欢迎的.
  • 该结构体可能相当大,在这种情况下,执行比必要更多的移动可能是不可取的(实际上,这不太可能是一个大问题,诚然).
  • 它只是感觉"不干净,因为移动"在技术上更像是更新".事实上,最简单的例子可能是像 *self = self.next.do_something() 这样的东西,它仍然会出现问题.
  • In some cases, there's no suitable dummy value. This is especially true if there's no public/easy way to construct a "zero value" for one or more of your struct members (e.g. what if the struct held a MutexGuard?). If the member you need to dummy-replace is in another module (or crate), you may be bound by difficult constraints of its construction that are undesireable when trying to build a dummy type.
  • The struct may be rather large, in which case doing more moves than is necessary may be undesirable (in practice, this is unlikely to be a big problem, admittedly).
  • It just "feels" unclean, since the "move" is technically more of an "update". In fact, the simplest example might be something like *self = self.next.do_something() which will still have problems.

在某些情况下,例如我展示的第一个 remove 片段,您也许可以更清楚地将其表示为 fn do_something(self) ->Self,但在其他情况下,例如 IntoIterator 示例,这无法完成,因为您受到 trait 定义的约束.

In some cases, such as that first remove snippet I showed, you could perhaps more cleanly represent it as a fn do_something(self) -> Self, but in other cases such as the IntoIterator example this can't be done because you're constrained by the trait definition.

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

Is there any better, cleaner way to do this sort of in-place update?

推荐答案

无论如何我们都需要赋值,mem::replace, mem::swap,或类似的东西.因为给定一个对象的 &mut 引用,没有办法将这个对象(或它的任何字段)移出而不用有效的东西替换它的内存区域,只要 Rust 禁止对未初始化的引用记忆.

In any case we'll need assignment, mem::replace, mem::swap, or something like that. Because given a &mut reference to an object there is no way to move this object (or any of it's fields) out without replacing it's memory area with something valid, as long as Rust forbids references to uninitialized memory.

至于用于替换的虚拟值,您始终可以通过使用某种包装类型为任何类型自行制作它们.例如,我经常使用 Option 来达到这个目的,其中 Some(T)T 类型的值,None 充当虚拟角色.这就是我的意思:

As for dummy values for replacement, you can always make them yourself for any type by using some wrapper type. For example, I often use Option for this purpose, where Some(T) is the value of type T, and None acts as dummy. This is what I mean:

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"),
        }
    }
}

游乐场链接

这篇关于如何使用和替换 &amp;mut ref 中的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆