Rust如何提供移动语义?

Rust如何提供移动语义?,rust,move-semantics,Rust,Move Semantics,这些声明将语义作为语言的一个特征。但我看不出移动语义是如何在Rust中实现的 锈盒是唯一使用移动语义的地方 let x = Box::new(5); let y: Box<i32> = x; // x is 'moved' 让x=Box::new(5); 设y:Box=x;//x被“移动” 上述锈蚀代码可以用C++编写为 auto x = std::make_unique<int>(); auto y = std::move(x); // Note the expli

这些声明将语义作为语言的一个特征。但我看不出移动语义是如何在Rust中实现的

锈盒是唯一使用移动语义的地方

let x = Box::new(5);
let y: Box<i32> = x; // x is 'moved'
让x=Box::new(5);
设y:Box=x;//x被“移动”

上述锈蚀代码可以用C++编写为

auto x = std::make_unique<int>();
auto y = std::move(x); // Note the explicit move
auto x=std::make_unique();
自动y=std::移动(x);//请注意明确的移动
据我所知(如果我错了,请纠正我)

  • Rust根本没有构造函数,更不用说移动构造函数了
  • 不支持右值引用
  • 无法使用右值参数创建函数重载

<>词义如何提供迁移语义?

我认为这是C++中的一个非常常见的问题。在C++中,当复制和移动时,你做的一切都是明确的。这种语言是围绕着复制和引用而设计的。有了C++11,“移动”东西的能力就粘在了这个系统上。另一方面,锈病又有了新的开始


Rust根本没有构造函数,更不用说移动构造函数了

您不需要移动构造函数。Rust移动“没有复制构造函数”的所有东西,也称为“没有实现
copy
trait”

Rust的默认构造函数(按照惯例)只是一个名为
new
的关联函数:

struct A(i32);
impl A {
    fn new() -> A {
        A(5)
    }
}
更复杂的构造函数应该有更具表现力的名称。这是C中的命名构造函数习惯用法++


不支持右值引用

这一直是一个要求的功能,请参见,但很可能您要求的是另一个功能:将内容移动到函数:

struct A;

fn move_to(a: A) {
    // a is moved into here, you own it now.
}

fn test() {
    let a = A;
    move_to(a);
    let c = a; // error, a is moved
}

无法使用右值参数创建函数重载

你可以通过特质来做到这一点

trait Ref {
    fn test(&self);
}

trait Move {
    fn test(self);
}

struct A;
impl Ref for A {
    fn test(&self) {
        println!("by ref");
    }
}
impl Move for A {
    fn test(self) {
        println!("by value");
    }
}
fn main() {
    let a = A;
    (&a).test(); // prints "by ref"
    a.test(); // prints "by value"
}

<>锈迹的移动和复制语义与C++有很大的不同。我将采用不同于现有答案的方法来解释它们


C++中,由于自定义复制构造函数,复制是一种可以任意复杂的操作。Rust不需要简单赋值或参数传递的自定义语义,因此采用了不同的方法

首先,传入的赋值或参数总是一个简单的内存副本

let foo = bar; // copies the bytes of bar to the location of foo (might be elided)

function(foo); // copies the bytes of foo to the parameter location (might be elided)
但是如果对象控制了一些资源呢?假设我们正在处理一个简单的智能指针,
Box

let b1 = Box::new(42);
let b2 = b1;
此时,如果只复制字节,是否会为每个对象调用析构函数(
drop
in Rust),从而释放同一指针两次并导致未定义的行为

答案是铁锈在默认情况下会移动。这意味着它将字节复制到新位置,然后旧对象就消失了。在上面第二行之后访问
b1
是一个编译错误。并且它不需要析构函数。该值已移动到
b2
,而
b1
也可能不再存在

这就是移动语义在Rust中的工作方式。字节被复制过来,旧对象就消失了

在一些关于C++移动语义的讨论中,Rust的方式被称为“破坏性移动”。有人建议添加“移动析构函数”或类似C++的东西,以便可以具有相同的语义。但是移动语义,因为它们在C++中实现,所以不这样做。旧对象被留下,其析构函数仍被调用。因此,您需要一个move构造函数来处理move操作所需的自定义逻辑。移动只是一个专门的构造函数/赋值操作符,它的行为方式是特定的


因此,默认情况下,Rust的赋值会移动对象,使旧位置无效。但许多类型(整数、浮点、共享引用)都有语义,其中复制字节是创建真实副本的一种非常有效的方法,而无需忽略旧对象。这些类型应该实现
Copy
特性,编译器可以自动派生该特性

#[derive(Copy)]
struct JustTwoInts {
  one: i32,
  two: i32,
}
这会向编译器发出信号,说明赋值和参数传递不会使旧对象无效:

let j1 = JustTwoInts { one: 1, two: 2 };
let j2 = j1;
println!("Still allowed: {}", j1.one);
注意,琐碎的复制和销毁的需要是相互排斥的;
Copy
类型也不能是
Drop


现在,如果你想复制一些仅仅复制字节还不够的东西,比如一个向量,那该怎么办?这方面没有语言特征;从技术上讲,该类型只需要一个函数来返回以正确方式创建的新对象。但按照惯例,这是通过实现
Clone
特性及其
Clone
功能来实现的。事实上,编译器也支持自动派生
Clone
,只需克隆每个字段

#[Derive(Clone)]
struct JustTwoVecs {
  one: Vec<i32>,
  two: Vec<i32>,
}

let j1 = JustTwoVecs { one: vec![1], two: vec![2, 2] };
let j2 = j1.clone();

现在,这有什么坏处吗?是的,事实上有一个相当大的缺点:因为将对象移动到另一个内存位置只是通过复制字节来完成的,而不是复制类型的自定义逻辑。事实上,Rust的生命周期系统使得安全构建此类类型成为不可能


但是,我认为,权衡是值得的。

< P> C++中的类和结构的默认赋值是浅拷贝。复制值,但不复制指针引用的数据。因此,修改一个实例会更改所有副本的引用数据。在另一个实例中,值(用于管理的f.e.)保持不变,可能导致状态不一致。移动语义可以避免这种情况。一个具有移动语义的内存管理容器的C++实现示例:

模板
类对象
{
T*p;
公众:
对象()
{
p=新T;
}
~object()
{
如果(p!=(T*)0)删除p;
}
模板//类型V用于允许在引用和值之间进行转换
object(object&v)//使用move语义复制构造函数
{
p=v.p;//移动所有权
v、 p=(T*)0;//确保它不会被删除
}
反对&
#[Derive(Clone)]
struct JustTwoVecs {
  one: Vec<i32>,
  two: Vec<i32>,
}

let j1 = JustTwoVecs { one: vec![1], two: vec![2, 2] };
let j2 = j1.clone();
#[derive(Copy, Clone)]
struct JustTwoInts { /* as before */ }