移动在引用范围内的东西,当这些引用将在移动后立即超出作用域时

  • 本文关键字:移动 引用 作用域 范围内 rust
  • 更新时间 :
  • 英文 :


我是从c++背景学习Rust的。我正在编写一个解析器,它读取有效的公式,如12 x (y - 4),并将它们放在抽象语法树(AST)中。我使用enum在AST中存储可能的节点,正如您在(非常简化的)代码中看到的那样。我遇到了一个问题。我想简化表达式-(-(12)),例如通过移动12,不是复制。一般来说,12可以用深度AST代替。目前我在函数simplify( .. )中确定了这种情况,但它无法编译。

我知道为什么它不编译:我试图移动的节点(即我的例子中的12)仍然在match子句中的引用范围内,所以这正是rust阻止我从引用可能出现的问题。但是在我的例子中,我知道这就是我想要的,而且我在*node = **child;行移动之后退出了函数,所以前面的引用将在那里超出范围。

是否有一种习惯的rust式的方法来解决这个问题?我真的不想复制双反子树。

#[derive(Debug)]
enum Node {
Num(i32),
UnaryMinus(Box<Node>),
}
use Node::*;
fn simplify(node: &mut Node) {
match &node {
Num(_) => (),
UnaryMinus(inner) => match &**inner {
Num(_) => (),
UnaryMinus(child) => {
// cannot move out of `**child` which is behind a shared reference
*node = **child;
return;
}
},
}
}
fn main() {
let double_minus = UnaryMinus(Box::new(UnaryMinus(Box::new(Num(12)))));
}

这里有两个问题:您具体得到的错误是因为您匹配的是&**inner而不是&mut **inner。从共享引用中移出是不允许的,但在某些情况下(我们将看到),从可变引用中移出是可以的。

修正后,您将得到关于可变引用的相同错误。那是因为你不能仅仅因为知道你是唯一持有它的人就移出一个可变引用。移动使其源处于未初始化状态,并且对未初始化内存的引用(可变的或共享的)永远都是不允许的。你必须在记忆中留下一些你正在摆脱的东西。你可以用std::mem::swap做到这一点。它接受两个可变引用并交换其内容。

现在,显然可以尝试调用std::mem::swap(node, &mut child),但这不会简单地工作,因为node已经在match表达式中可变地借用了,并且您不能在可变地借用两次。

此外,这会泄漏内存,因为您现在有一个引用周期,其中node -> inner -> node。这虽然在Rust中完全有效,但通常不是你想要的。

相反,你需要一些假人,你可以把child的地方。枚举的一些简单变体,一旦被inner删除,它就可以被安全删除。在本例中,可以是Node::Num(0):

#[derive(Debug)]
enum Node {
Num(i32),
UnaryMinus(Box<Node>),
}
// This is just to verify that everything gets dropped properly
// and we don't leak any memory
impl Drop for Node {
fn drop(&mut self) {
println!("dropping {:?}", self);
}
}
use Node::*;
fn simplify(node: &mut Node) {
// `placeholder` will be our dummy
let (mut can_simplify, mut placeholder) = (false, Num(0));
match node {
Num(_) => (),
// we'll need to borrow `child` mutably later, so we have to
// match on `&mut **inner` not `&**inner`
UnaryMinus(inner) => match &mut **inner {
Num(_) => (),
UnaryMinus(ref mut child) => {
// move the contents of `child` into `placeholder` and vice versa
std::mem::swap(&mut placeholder, child);
can_simplify = true;
}
},
}
if can_simplify {
// now we can safely move into `node`
*node = placeholder;
// you could skip the conditional if all other, non-simplifying
// branches return before this statement
}
}
fn main() {
let mut double_minus = UnaryMinus(Box::new(UnaryMinus(Box::new(Num(12)))));
simplify(&mut double_minus);
println!("{:?}", double_minus);
}

最新更新