可变引用有移动语义吗?

问题描述 投票:0回答:1
fn main() {
    let mut name = String::from("Charlie");
    let x = &mut name;
    let y = x;       // x has been moved
    say_hello(y);
    say_hello(y);       // but y has not been moved, it is still usable
    change_string(y);
    change_string(y);  

}

fn say_hello(s: &str) {
    println!("Hello {}", s);
}

fn change_string(s: &mut String) {
    s.push_str(" Brown");
}

当我将

x
分配给
y
时,
x
已被移动。但是,当我在函数中使用具有移动语义的东西时,我希望它会被移动。但是,在后续调用后我仍然可以使用该参考。也许这与 say_hello() 采用不可变引用有关,但 change_string() 采用可变引用但引用仍然没有移动。

rust borrow-checker ownership
1个回答
52
投票

您的推理和观察都是完全正确的。看起来事情确实应该按照你描述的方式发生。然而,编译器在这里应用了一些方便的魔法。 移动语义通常适用于 Rust 中所有未实现

Copy

特征的类型。共享引用是

Copy
,因此它们在分配或传递给函数时只需复制即可。可变引用不是
Copy
,因此应该移动它们。
这就是魔法开始的地方。每当将可变引用分配给编译器已知为可变引用的类型的名称时,原始引用将被“隐式重新借用”而不是被移动。所以函数调用

change_string(y); 被编译器转换为意思

change_string(&mut *y);

原始引用被解除引用,并创建一个新的可变借用。这个新的借用被移动到函数中,并且一旦函数返回,原始的借用就会被释放。

请注意,这不是函数调用和赋值之间的区别。当编译器已知目标类型是可变引用时,就会发生隐式重借,例如因为该模式具有显式类型注释。因此,这一行还创建了隐式重新借用,因为我们显式地将其注释为可变引用类型:

let y: &mut _ = x;

另一方面,此函数调用会移动(并因此消耗)可变引用

y

fn foo<T>(_: T) {} [...] foo(y);

这里的泛型类型

T
并不是显式的可变引用类型,因此即使编译器推断该类型是可变引用,也不会发生隐式重借——就像您的赋值
let y = x;

的情况一样。

在某些情况下,即使没有显式类型注释,编译器也可以推断泛型类型是可变引用:
fn bar<T>(_a: T, _b: T) {}

fn main() {
    let mut i = 42;
    let mut j = 43;
    let x = &mut i;
    let y = &mut j;
    bar(x, y);   // Moves x, but reborrows y.
    let _z = x;  // error[E0382]: use of moved value: `x`
    let _t = y;  // Works fine. 
}

在推断第一个参数的类型时,编译器还不知道它是可变引用,因此不会发生隐式重借,并且

x
 被移动到函数中。然而,当到达第二个参数时,编译器已经推断出 
T

是可变引用,因此

y
被隐式重新借用。 (这个例子很好地说明了为什么添加编译器魔法来让事情“正常工作”通常是一个坏主意。显式比隐式更好。)
不幸的是,
这种行为目前没有记录在 Rust 参考文献中

另请参阅:

填充 Identity 函数的功能(在 Rust 中)

© www.soinside.com 2019 - 2024. All rights reserved.