在“if let Some(ref mut x) = option”和“if let Some(x) = option.as_mut()”中匹配可变选项引用有什么区别?

luc*_*ler 10 reference mutable rust

背景

考虑一个玩具问题,其中我有一个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
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我将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
}
Run Code Online (Sandbox Code Playgroud)

编译错误:

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
}
Run Code Online (Sandbox Code Playgroud)

题

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

if let Some(ref mut x) = tail
Run Code Online (Sandbox Code Playgroud)

和

if let Some(x) = tail.as_mut()
Run Code Online (Sandbox Code Playgroud)

?

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

更新

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

更新 2

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

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
}
Run Code Online (Sandbox Code Playgroud)

新的错误:

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
}
Run Code Online (Sandbox Code Playgroud)

Ema*_*oun 5

代码的第二个版本失败的原因是因为 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
...
Run Code Online (Sandbox Code Playgroud)

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

现在,为什么第一个版本可以build_list工作?简而言之:因为tail从来没有借过。是对其组件if let Some(ref mut x) = tail的解构(在本例中是和)。这不是整体借用,只是借用。然后,您现在还可以解构为其组件(仅提取),并使用 借用它。在下一次迭代中,不会被借用,因此可以愉快地重新分配。tailOption::Somextailxtail = &mut x.nextxnexttailtail

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

  • 如果你这样做的话,我认为程序的逻辑是不一样的。但是,它确实可以编译,因为如果将所有尾部分配给“head”,则“tail”永远不会借用自己,它们都会借用“head”。 (2认同)