先序文章请看:
面向C++程序员的Rust教程(一)
所有权与移动语义
要说Rust语言跟其他语言最大的区别,那笔者觉得非数这个所有权和移动语义莫属。
深浅复制
对于绝大多数语言来说,变量/对象之间的赋值通常都是复制语义。例如C++中:
void Demo() {
Obj o1; // 对象1
auto o2 = o1; // 复制语义,o2是o1的复制
}
只不过深复制还是浅复制需要进一步研究。C++中由于完全支持栈上部署自定义类型以及自定义的拷贝构造/赋值函数,程序员需要自行判断内部指针/引用关系,决定使用深复制或是浅复制。
一些语言是把「结构体」和「类」做区分,结构体仅用于做数据聚合,部署在栈上,而类则添加更多OO特性,部署在堆上(然后栈上给一个指针)。比如说Swift和C#就是如此。那么这种情况下栈上部署的类型,复制就为深复制,而堆上部署的类型复制就为浅复制。
还有一些语言索性不允许自定义类型在栈上部署(比如java、OC),那么这种情况下也就是限定了默认的复制均为浅复制,例如下面OC的例子:
void Demo() {
Object *o1 = [[Object alloc] init];
Object *o2 = o1; // 由于栈上只有指针,因此复制一定是浅复制
}
总之,统一的原则都是「栈上做深复制」,所以如果栈上是完整数据那么就是深复制,如果栈上只有指针/引用,那么就是浅复制。
rust移动语义
但Rust非常特殊,他根本不在这里纠结深复制还是浅复制的问题,而Rust默认为「移动语义」而非「复制语义」。当然,这只针对自定义类型来说,对于整数、浮点数这些它仍然是简单的值复制。我们来看一个例子:
fn main() {
let mut a = 5;
let b = a;
a = 10;
println!("{},{}", a, b); // 10,5
}
这种基本类型看上去无可厚非,但如果换成自定义类型结果可能大大超出预期:
struct Test {
a: i32,
b: i32
}
fn main() {
let mut t = Test{
a: 1, b: 2};
let t2 = t;
t.a = 8; // ERROR
}
我们会发现,在尝试更改t.a
的时候,编译报错了,报错信息如下: