为什么重新启动后移动的变量的语义不是移动?

ZyS*_*ZyS 7 ownership rust

最小的例子是这样的(Playground):

#[derive(Debug)]
struct Node {
    id: usize,
}

fn main() {
    let mut node = Node { id: 0 };
    let mut lastnode = &mut node;
    let mut last = lastnode; // move
    (*last).id = 1;
    println!("{:?}", last);
    //println!("{:?}", lastnode);

    let mut node2 = Node { id: 2 };
    lastnode = &mut node2;
    last = lastnode; // doesn't move to "last"
    println!("{:p}", last); // "last" and "lastnode" point to the same address
    println!("{:p}", lastnode);
}
Run Code Online (Sandbox Code Playgroud)

为什么第一个会last = lastnode导致所有权移动,但第二个last = lastnode似乎只遵循借用语义?

E_n*_*ate 9

这是编译器借用语义与非词法生命周期相结合的一个相当复杂的情况。让我们一步一步地看一下这个过程。

let mut node = Node { id: 0 };
let mut lastnode = &mut node; 
let mut last = lastnode; // move
Run Code Online (Sandbox Code Playgroud)

这行确实lastnode移入了last,这可以通过取消注释后面的行来证明:

//println!("{:?}", lastnode);
Run Code Online (Sandbox Code Playgroud)

如果我们取消注释这一点,编译器的错误将是:

error[E0382]: borrow of moved value: `lastnode`
  --> src/main.rs:12:22
   |
8  |     let mut lastnode = &mut node;
   |         ------------ move occurs because `lastnode` has type `&mut Node`, which does not implement the `Copy` trait
9  |     let mut last = lastnode; // move
   |                    -------- value moved here
...
12 |     println!("{:p}", lastnode);
   |                      ^^^^^^^^ value borrowed here after move
Run Code Online (Sandbox Code Playgroud)

无论如何,可以通过显式重新借用来使此代码工作lastnode,如下所示。

let mut last = &mut *lastnode; // move no more!
Run Code Online (Sandbox Code Playgroud)

通过重新借用,一旦当前绑定的引用不再使用,last将返回借用。稍后仅进行重新分配,将其绑定到另一个引用,因此不会导致任何冲突。lastnodelast

最有趣的部分可能是,尽管操作的结构和顺序是相同的,但事情似乎只是在后续步骤中进行。

let mut node2 = Node { id: 2 };
lastnode = &mut node2;
last = lastnode; // doesn't move to "last"
println!("{:p}", last); // "last" and "lastnode" point to the same address
println!("{:p}", lastnode);
Run Code Online (Sandbox Code Playgroud)

如果lastnode已移入last,那么编译器将不会让我们调用println!("{:p}", lastnode)last = lastnode是一个隐式的 reborrow,由于非词汇生命周期,它在最后一行之前被删除。

考虑到隐式重借是编译器的一个记录不足的功能,它归结为一种边缘情况,即当前编译器不知道隐式重借可变引用以满足给定的代码。或者换句话说,当前版本的编译器只能使代码的第二部分工作,但不能使第一部分工作。

其根本原因很可能是关于两个变量如何键入的实现细节。虽然lastnode总是分配对节点的可变引用,last但通过对 的多次分配来定义lastnode。当发生多个赋值时,编译器会第二次重新借用它,可能是为了完成它被强制执行的生命周期。无论如何,这与语言的任何不变性无关,并且可能在未来的版本中发生变化。

为了完整起见,这里是隐式重借操作的另一个最小示例,其中添加冗余赋值使代码可以编译。

#[derive(Debug)]
struct Node;

let mut node = Node;
let mut node2 = Node;
let lastnode;
let mut last;
last = &mut node; // <-- remove this to fail
lastnode = &mut node2;
last = lastnode;
println!("{:p}", last);
println!("{:p}", lastnode);
Run Code Online (Sandbox Code Playgroud)

操场

也可以看看: