可变引用是不是具有移动语义?

Posted

技术标签:

【中文标题】可变引用是不是具有移动语义?【英文标题】:Do mutable references have move semantics?可变引用是否具有移动语义? 【发布时间】:2020-11-07 15:36:26 【问题描述】:
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() 采用可变引用但引用仍未移动有关。

【问题讨论】:

【参考方案1】:

您的推理和观察都完全正确。看起来事情确实应该按照你描述的方式发生。但是,编译器在这里应用了一些便利魔法。

移动语义通常适用于 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 被隐式重借。 (这个例子很好地说明了为什么添加编译器魔法来使事情“正常工作”通常是一个坏主意。显式优于隐式。)

很遗憾,this behaviour currently isn't documented in the Rust reference。

另见:

Stuff the Identity Function Does (in Rust) Discussion of the topic on the Rust users forum Why is the mutable reference not moved here?

【讨论】:

再借有什么好处?它会提高性能吗?它可以防止某种错误吗? @Shiva 另一种方法是移动可变引用,这意味着它不能再次使用。性能不会受到任何影响——生成的代码应该或多或少相同。不同之处在于借用检查器如何解释它。

以上是关于可变引用是不是具有移动语义?的主要内容,如果未能解决你的问题,请参考以下文章

C++进阶第二十五篇——C++11(列表初始化+变量类型推导+右值引用和移动语义+新的类功能+可变模板参数)

C++进阶第二十五篇——C++11(列表初始化+变量类型推导+右值引用和移动语义+新的类功能+可变模板参数)

右值引用,移动语义,完美转发

移动语义+对成员成员变量的引用 - 解决方案和命名法?

首先创建字符串然后通过移动语义将其添加到向量或在向量中创建元素是不是具有内存效率?

当基类提供时,派生类是不是需要实现移动语义?