Skip to content

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);
}

Released under the MIT License