【问题标题】:Why is an explicit borrow required in Rust tuple pattern matching?为什么在 Rust 元组模式匹配中需要显式借用?
【发布时间】:2018-05-10 03:42:26
【问题描述】:

我正在用 Rust 写一棵二叉树,借用检查器真的让我很困惑。这里是a minimal example that reproduces the problem

二叉树定义如下:

struct NonEmptyNode;

pub struct BinaryTree {
    root: Option<NonEmptyNode>,
}

借用检查器拒绝的代码是:

// Implementation #1
fn set_child_helper(&self, bt: &Self, setter: fn(&NonEmptyNode, &NonEmptyNode)) -> bool {
    match (self.root, bt.root) {
        (Some(ref rt), Some(ref node)) => {
            setter(rt, node);
            true
        }
        _ => false,
    }
}

错误信息是

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:10:16
   |
10 |         match (self.root, bt.root) {
   |                ^^^^ cannot move out of borrowed content

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:10:27
   |
10 |         match (self.root, bt.root) {
   |                           ^^ cannot move out of borrowed content

要使其工作,必须将代码修改为:

// Implementation #2
fn set_child_helper(&self, bt: &Self, setter: fn(&NonEmptyNode, &NonEmptyNode)) -> bool {
    match (&self.root, &bt.root) {
        // explicit borrow
        (&Some(ref rt), &Some(ref node)) => {
            // explicit borrow
            setter(rt, node);
            true
        }
        _ => false,
    }
}

如果我在没有显式借用的情况下一次对一个变量进行模式匹配,则借用检查器根本不会抱怨:

// Implementation #3
fn set_child_helper(&self, bt: &Self, setter: fn(&NonEmptyNode, &NonEmptyNode)) -> bool {
    match self.root {
        Some(ref rt) => match bt.root {
            // No explict borrow will be fine
            Some(ref node) => {
                // No explicit borrow will be fine
                setter(rt, node);
                true
            }
            _ => false,
        },
        _ => false,
    }
}

为什么实现 #3 不需要显式借用,而实现 #1 需要?

【问题讨论】:

  • 巧合的是,在您发布此问题后 仅几个小时Rust 1.26 已发布,其中包括(除其他很酷的东西)match 模式的自动引用和取消引用语义.这意味着在匹配(&amp;self.root, &amp;bt.root) 时,您可以编写(Some(rt), Some(node)),编译器会智能地将rtnode 转换为引用。 (playground)

标签: rust pattern-matching move-semantics


【解决方案1】:

关键是self.rootbt.root"place expression"s,而元组不是。 #3 起作用的原因是编译器知道如何“通过”中间表达式以绑定到原始存储位置。

你可以用另一种方式看待它:像self.root 这样的非常简单的表达式的特殊之处在于它们看起来和行为都像值(并且具有值类型),但编译器会秘密地记住它是如何达到该值的,从而允许它返回并获取指向该值的读取位置的指针。

判断某事物是否为“位置表达式”的简单方法是尝试为其赋值。如果你能做到expr = some_value;,那么expr一定是一个“地方表达式”。顺便说一句,这也是为什么当您编写 &amp;self.root 时,您会得到一个指向 self.root 存储位置的指针,而不是指向 self.root 副本的指针。

这种“位置表达式”业务不适用于元组,因为它们没有这个属性。要构造元组,编译器必须实际读取元组元素的值并将它们移动或复制到元组的新存储中。这会破坏编译器可能拥有的任何位置关联:这些值字面上不再是它们以前的位置。

最后,您可能想查看Option::as_ref,它将&amp;Option&lt;T&gt; 变成Option&lt;&amp;T&gt;。这会让你在(self.root.as_ref(), bt.root.as_ref()) 上匹配(Some(rt), Some(node)) 之类的模式,这可能更方便。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-15
    • 2020-11-01
    • 2015-08-05
    • 2016-07-22
    • 2016-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多