Rust 如何使用和替换&;中的值;多参考
有时我会遇到一个问题,由于实现细节对用户来说是不可见的,因此我需要“销毁”一个Rust 如何使用和替换&;中的值;多参考,rust,Rust,有时我会遇到一个问题,由于实现细节对用户来说是不可见的,因此我需要“销毁”一个&mut,并将其替换到内存中。这通常发生在递归方法或递归结构上的迭代器实现中。它通常采用以下形式: fn create_something(self); pub fn do_something(&mut self) { // What you want to do *self = self.create_something(); } 我在当前项目中碰巧遇到的一个例子是在我编写的KD树中,当我
&mut
,并将其替换到内存中。这通常发生在递归方法或递归结构上的迭代器实现中。它通常采用以下形式:
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怎么办?),则尤其如此。如果需要虚拟替换的构件位于另一个模块(或板条箱)中,则在尝试构建虚拟类型时,可能会受到其构造的困难约束的约束
- 结构可能相当大,在这种情况下,进行更多的移动可能是不可取的(事实上,这不太可能是一个大问题)
- 它只是“感觉”不干净,因为“移动”在技术上更像是“更新”。事实上,最简单的例子可能是像
这样的东西,它仍然会有问题*self=self.next.do_something()
remove
片段,您可能可以更清晰地将其表示为fn do_something(self)->self
,但在其他情况下,例如IntoIterator示例,这无法完成,因为您受到特征定义的约束
有没有更好、更干净的方法来进行这种就地更新?在任何情况下,我们都需要赋值、
mem::replace
、mem::swap
,或者类似的东西。因为给定一个对象的&mut
引用,如果不将其内存区域替换为有效的内容,就无法将该对象(或其任何字段)移出,只要Rust禁止引用未初始化的内存
至于用于替换的伪值,您始终可以使用一些包装器类型为任何类型自己生成它们。例如,我经常为此使用选项
,其中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"),
}
}
}
struct树(选项);
枚举节点{
叶(T),
儿童(Vec),
}
impl树,其中T:PartialEq{
fn删除(&mut self,值:&T){
匹配self.0.take(){
如果叶值==值=>
(),
node@Some(node::Leaf(..)=>
*self=树(节点),
一些(节点::子节点(节点_子节点))=>{
让孩子们:Vec=
节点_子节点
.into_iter()
.filter_映射(| mut tree |{tree.remove(value);tree.0})
.map(|节点|树(部分(节点)))
.收集();
if!children.is_为空(){
*self=Tree(一些节点::子节点(Children));
}
},
无=>
惊慌失措!(“出了问题”),
}
}
}
我发现最好的处理方法是使用
fn do_something(&mut self)
而不是fn do_something(self)->self
(或者两者都有助于API工效学,后者可以实现为fn by_value(mut self)->self{self.mutating();self}
)与&mut
相关的安全接口将是一个函数,该函数采用&mut T
和Fn(T)->T
。然后,安全接口可以在内部对引用使用ptr::read
,该引用为您提供一个拥有的对象,然后将该对象传递给Fn
。问题是,当传递的Fn
崩溃时,您没有有效的对象进行写回,并且您传递给Fn
的对象将已经调用其析构函数。在这种情况下,您别无选择,只能中止,因为如果您不这样做,实际对象现在也将调用其析构函数。这里是关于消费和替换一个值,另一个问题是关于用另一个值替换一个值。这个问题的解决方案可以是使用“替换为”板条箱: