这是一个相当复杂的编译器借用语义问题,结合了非词法生命周期。让我们一步一步地走过这个问题。
let mut node = Node { id: 0 };
let mut lastnode = &mut node;
let mut last = lastnode;
这行代码将lastnode
移动到了last
,可以通过取消后面的注释来证明:
如果我们取消对这个的注释,编译器将会报错:
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
就算价值不高,也可以通过显式重新借用lastnode
来使此代码正常工作,如下所示。
let mut last = &mut *lastnode;
使用重新借用(reborrow)的方式,当绑定到last
的引用不再被使用时,它会将借用返回给lastnode
。稍后只有重新赋值,将其绑定到另一个引用,因此不会产生任何冲突。
最有趣的部分可能是,尽管操作的结构和顺序相同,在后续步骤中似乎一切“正常运行”。
let mut node2 = Node { id: 2 };
lastnode = &mut node2;
last = lastnode;
println!("{:p}", last);
println!("{:p}", lastnode);
如果
lastnode
被移动到
last
中,那么编译器就不会允许我们调用
println!("{:p}", lastnode)
。
last = lastnode
是一个
隐式重新借用,在最后一行代码之前被丢弃,这要归功于非词法生命周期。
考虑到
隐式重新借用是编译器文档不完整的特性,它归结为一种边缘情况,即当前编译器不知道如何隐式重新借用可变引用以满足所给定的代码。换句话说,当前版本的编译器只能使第二部分的代码工作,但不能使第一部分工作。
这背后的原因可能是两个变量类型如何定义的实现细节。虽然
lastnode
始终被分配一个对节点的可变引用,但
last
通过多次分配给
lastnode
而定义。当进行多个赋值时,这促使编译器第二次重新借用它,可能是为了满足它所强制的生命周期。无论如何,这与语言的任何不变量都没有关系,并且在未来版本中可能会改变。
为了完整起见,这里是隐式重新借用的另一个最小示例,其中添加一个冗余赋值使代码编译。
#[derive(Debug)]
struct Node;
let mut node = Node;
let mut node2 = Node;
let lastnode;
let mut last;
last = &mut node;
lastnode = &mut node2;
last = lastnode;
println!("{:p}", last);
println!("{:p}", lastnode);
Playground
另外还有:
last
的可变借用设置为last = lastnode; println!("{:p}", last);
这两行代码,然后last
被销毁,lastnode
的可变借用继续存在。如果你尝试同时使用这两个可变借用,就会出现双重借用错误。 - Alexey Larionovprintln!("{:?}", last)
,编译器会报错:println!("{:p}", lastnode); // immutable borrow occurs here
,而println!("{:p}", last);//mutable borrow later used here
。但这并不是移动语义。 - ZyS