【问题标题】:What is the difference between matching a mutable Option reference in "if let Some(ref mut x) = option" and in "if let Some(x) = option.as_mut()"?在“if let Some(ref mut x) = option”和“if let Some(x) = option.as_mut()”中匹配可变选项引用有什么区别?
【发布时间】:2020-07-13 04:55:04
【问题描述】:

背景

考虑一个玩具问题,其中我有一个 Node 结构,它表示链表的节点,我想创建一个函数来构建一个值从 1 到 9 的列表。以下代码按预期工作:

struct Node {
    val: i32,
    next: Option<Box<Node>>,
}

fn build_list() -> Option<Box<Node>> {
    let mut head = None;
    let mut tail = &mut head;
    for n in 1..10 {
        *tail = Some(Box::new(Node {val: n, next: None}));
        if let Some(ref mut x) = tail {
            tail = &mut x.next;
        };
    }
    head
}

但是如果我将build_list函数中的匹配表达式修改为如下,编译失败:

fn build_list() -> Option<Box<Node>> {
    let mut head = None;
    let mut tail = &mut head;
    for n in 1..10 {
        *tail = Some(Box::new(Node {val: n, next: None}));
        if let Some(x) = tail.as_mut() {
            tail = &mut x.next;
        };
    }
    head
}

编译错误:

error[E0506]: cannot assign to `*tail` because it is borrowed
  --> src/main.rs:72:9
   |
72 |         *tail = Some(Box::new(Node {val: n, next: None}));
   |         ^^^^^
   |         |
   |         assignment to borrowed `*tail` occurs here
   |         borrow later used here
73 |         {
74 |             if let Some(x) = tail.as_mut() {
   |                              ---- borrow of `*tail` occurs here

error[E0499]: cannot borrow `*tail` as mutable more than once at a time
  --> src/main.rs:74:30
   |
74 |             if let Some(x) = tail.as_mut() {
   |                              ^^^^ mutable borrow starts here in previous iteration of loop

问题

在这个例子中,有什么区别

if let Some(ref mut x) = tail

if let Some(x) = tail.as_mut()

?

(作为一个学习 Rust 的初学者)我希望这些匹配表达式是等价的,但显然我缺少一些细微的差异。

更新

我清理了原始示例中的代码,这样我就不需要列表头部的占位符元素了。差异(和编译错误)仍然存在,我只是得到一个额外的编译错误分配给借来的*tail

更新 2

(这只是一个奇怪的观察,无助于回答原始问题) 在考虑了@Emoun 的回答之后,编译器应该能够知道tail 在循环的每次迭代中都在变化(这样它可以确保每次都借用&amp;mut x.next)这听起来很重要(在第一个工作示例中)不同)。所以我做了一个实验,通过在tail = &amp;mut x.next; 赋值中添加if n % 2 == 0 条件,以编译器无法判断是否是这种情况的方式更改代码。果然,导致了和另外一个类似的编译错误:

fn build_list() -> Option<Box<Node>> {
    let mut head = None;
    let mut tail = &mut head;
    for n in 1..10 {
        *tail = Some(Box::new(Node {val: n, next: None}));
        if let Some(ref mut x) = tail {
            if n % 2 == 0 {
                tail = &mut x.next;
            }
        };
    }
    head
}

新的错误:

error[E0506]: cannot assign to `*tail` because it is borrowed
  --> src/main.rs:60:9
   |
60 |         *tail = Some(Box::new(Node {val: n, next: None}));
   |         ^^^^^
   |         |
   |         assignment to borrowed `*tail` occurs here
   |         borrow later used here
61 |         if let Some(ref mut x) = tail {
   |                     --------- borrow of `*tail` occurs here

error[E0503]: cannot use `*tail` because it was mutably borrowed
  --> src/main.rs:61:16
   |
61 |         if let Some(ref mut x) = tail {
   |                ^^^^^---------^
   |                |    |
   |                |    borrow of `tail.0` occurs here
   |                use of borrowed `tail.0`
   |                borrow later used here

error[E0499]: cannot borrow `tail.0` as mutable more than once at a time
  --> src/main.rs:61:21
   |
61 |         if let Some(ref mut x) = tail {
   |                     ^^^^^^^^^ mutable borrow starts here in previous iteration of loop

【问题讨论】:

  • if let Some(x) = tail 似乎也有效。不幸的是,我对任何一种情况都没有解释。
  • tail.as_mut() 的返回值的生命周期似乎与tail 相同,因此只是编译器限制。在循环内移动let tail = &amp;mut head 使其工作。
  • This 看起来非常相似。
  • 谁想知道为什么Some(x) 可以匹配&amp;mut Option&lt;Box&lt;Node&gt;&gt; 的类型应该寻找rust match ergonomics
  • @CoronA , if let Some(x) = tail 确实有效,这也让我感到惊讶(学到了新东西,谢谢!)。

标签: rust reference mutable


【解决方案1】:

你的代码的第二个版本失败的原因是因为 rust 的方法/函数总是借用整个对象,而不是它们的一部分。

在您的情况下,这意味着tail.as_mut() 可变地借用tail,并且只要使用tail,这种借用就会保持有效:

...
    for n in 1..10 {
        *tail = Some(Box::new(Node {val: n, next: None})); // Error in the second iteration,
                                                           // 'tail' was already borrowed
        if let Some(x) = tail.as_mut() { // <-+ Borrow of 'tail' starts in the first iteration
            tail = &mut x.next;          // <-+ 'tail' now borrows itself
        };                               //   |
    }                                    // <-+ Borrow of 'tail' ends here, after the last iteration
...

由于xtail 的借用,&amp;mut x.next 也是tail 的借用,这意味着tail = &amp;mut x.nexttail 借用自身。因此,只要tail 在范围内,tail 的初始借用就不会超出范围。每次迭代都使用tail,所以借用只能在循环的最后一次迭代后超出范围。

现在,为什么build_list 的第一个版本可以工作?简而言之:因为tail 永远不会被借用。 if let Some(ref mut x) = tail 是将tail 解构为它的组件(在本例中为Option::Somex)。这不是整体借用tail,它只是借用x。当您然后tail = &amp;mut x.next 时,您现在还将x 解构为它的组件(仅提取next),并使用tail 借用它。在下一次迭代中,tail 不会被借用,因此可以很高兴地重新分配。

方法/函数调用的局限性在于它们不知道您以后将使用对象的哪些部分。因此,as_mut() 必须借用整个tail,即使您只使用了其中的一部分。这是类型系统的限制,也是 getter/setter 方法比直接调用结构的成员更弱/更受限制的原因之一:getter/setter 将强制您借用整个结构,而直接访问成员只会借用它成员而不是其他人。

【讨论】:

  • 这仍然不能解释为什么它适用于tail = &amp;mut head(而不是&amp;mut x.nextas_mut() 案例。
  • 如果你这样做,我认为程序的逻辑是不一样的。但是,它确实可以编译,因为如果你将所有尾部分配给 head,你永远不会有 tail 借用自己,它们都会借用 head
  • 我相信逻辑不是这种情况下的重点,我也不认为 借用本身 是一个正确的论点,因为tail 是一个指针,tail'调用tail.as_mut() 后,s pointee 将被重新借用,然后tail 将可变借用x.next。有了所有这些,是的x 被多次借用可变(不是tail)。如果你使用tail = &amp;mut head,我们的x将会是head,而head将会被多次借用为可变的,那么为什么它允许多次借用head呢?
  • @luca_moller x 在每次迭代中都是不同的变量,因为您使用 if let Some(ref mut x) = tail 创建了一个新变量。因此,没有任何与x 相关联的借用从一次迭代到下一次迭代仍然存在。是的,借用x 允许编译器看到从一次迭代到下一次迭代没有任何借用。
  • 感谢您让我知道借用检查器有更多功能 :) ... 只需忽略 as_mut&amp;mut *tail 也违反了与as_mut() 相同的借用检查器规则。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多