Appearance
11.3 复制语义
默认的 move 语义是 Rust 的一个重要设计,但是任何时候需要复制都去调用 clone
函数会显得非常烦琐。对于一些简单类型,比如整数、bool,让它们在赋值的时候默认采用复制操作会让语言更简单。
比如下面这个程序就可以正常编译通过:
rust
fn main() {
let v1 : isize = 0;
let v2 = v1;
println!("{}", v1);
}
编译器并没有阻止 v1
被使用,这是为什么呢?
因为在 Rust 中有一部分“特殊照顾”的类型,其变量绑定操作是 copy 语义。所谓的 copy 语义,是指在执行变量绑定操作的时候,v2
是对 v1
所属数据的一份复制。v1
所管理的这块内存依然存在,并未失效,而 v2
是新开辟了一块内存,它的内容是从 v1
管理的内存中复制而来的。和手动调用 clone
方法效果一样,let v2 = v1;
等效于 let v2 = v1.clone();
。
使用文件系统来打比方。copy 语义就像“复制、粘贴”操作。操作完成后,原来的数据依然存在,而新的数据是原来数据的复制品。 move 语义就像“剪切、粘贴”操作。操作完成后,原来的数据就不存在了,被移动到了新的地方。这两个操作本身是一样的,都是简单的内存复制,区别在于复制完以后,原先那个变量的生命周期是否结束。
Rust 中,在普通变量绑定、函数传参、模式匹配等场景下,凡是实现了 std::marker::Copy
trait 的类型,都会执行 copy 语义。基本类型,比如数字、字符、bool 等,都实现了 Copy trait,因此具备 copy 语义。
对于自定义类型,默认是没有实现 Copy trait 的,但是我们可以手动添上。示例如下:
rust
struct Foo {
data : i32
}
impl Copy for Foo {}
fn main() {
let v1 = Foo { data : 0 };
let v2 = v1;
println!("{:?}", v1.data);
}
编译错误。错误信息是
error[E0277]: the trait bound `Foo: Clone` is not satisfied
--> src/main.rs:5:15
|
5 | impl Copy for Foo {}
| ^^^ the trait `Clone` is not implemented for `Foo`
查一下文档发现,原来 Copy 继承了 Clone,我们要实现 Copy trait 必须同时实现 Clone trait。把代码改成:
rust
struct Foo {
data : i32
}
impl Clone for Foo {
fn clone(&self) -> Foo {
Foo { data : self.data }
}
}
impl Copy for Foo {}
fn main() {
let v1 = Foo { data : 0 };
let v2 = v1;
println!("{:?}", v1.data);
}
编译通过。现在 Foo 类型也拥有了复制语义。在执行变量绑定、函数参数传递的时候,原来的变量不会失效,而是会新开辟一块内存,将原来的数据复制过来。
绝大部分情况下,实现 Copy trait 和 Clone trait 是一个非常机械化的、重复性的工作,clone 方法的函数体要对每个成员调用一下 clone 方法。Rust 提供了一个编译器扩展 derive attribute,来帮我们写这些代码,其使用方式为#[derive(Copy, Clone)]
。只要一个类型的所有成员都具有 Clone trait,我们就可以使用这种方法来让编译器帮我们实现 Clone trait 了。
示例如下:
rust
#[derive(Copy, Clone)]
struct Foo {
data : i32
}
fn main() {
let v1 = Foo { data : 0 };
let v2 = v1;
println!("{:?}", v1.data);
}