复制值,第二部分

让我们考虑与之前相同的例子,但稍作调整:使用u32代替String作为类型。

#![allow(unused)]
fn main() {
fn consumer(s: u32) { /* ... */ }fn example() {
     let s: u32 = 5;
     consumer(s);
     let t = s + 1; // 无错误!}}
}

这将无误编译!这是怎么回事?Stringu32之间的区别是什么,使得后者无需.clone()就能工作?

Copy

Copy是Rust标准库中定义的另一个特性:

#![allow(unused)]
fn main() {
pub trait Copy: Clone { }
}

它是一个标记特性,类似于Sized

如果一个类型实现了Copy,创建该类型的实例时就不需要显式调用.clone():Rust会隐式地为你处理。u32就是一个实现Copy的类型示例,因此上述代码能无误编译:当调用consumer(s)时,Rust通过对s进行位级复制来创建一个新的u32实例,然后将这个新实例传递给consumer。这一切都在幕后自动完成,无需你的介入。

什么可以是Copy

Copy并不等同于“自动克隆”,尽管它暗示了这一点。类型必须满足一些条件才能被允许实现Copy

首先,它必须实现Clone,因为CopyClone的子特性。 这是有道理的:如果Rust能够_隐式_创建类型的实例,那么通过调用.clone()也应该能够_显式_创建新实例。

但这还不是全部。还需满足几个其他条件:

  1. 类型不管理任何额外资源(如堆内存、文件句柄等),除了它在内存中占用的std::mem::size_of字节。
  2. 类型不是可变引用(&mut T)。

如果这两个条件都满足,那么Rust就可以通过执行原实例的位级复制安全地创建一个新实例——这常被称为memcpy操作,源自C标准库中执行位级复制的函数。

案例研究1:String

String是一个不实现Copy的类型。 为什么?因为它管理着额外的资源:用于存储字符串数据的堆分配内存缓冲区。

假设Rust允许String实现Copy。 那么,当通过位级复制原始实例创建新的String实例时,原始实例和新实例都将指向同一内存缓冲区:

               s                                 copied_s
 +---------+--------+----------+      +---------+--------+----------+
 | pointer | length | capacity |      | pointer | length | capacity |
 |  |      |   5    |    5     |      |  |      |   5    |    5     |
 +--|------+--------+----------+      +--|------+--------+----------+
    |                                    |
    |                                    |
    v                                    |
  +---+---+---+---+---+                  |
  | H | e | l | l | o |                  |
  +---+---+---+---+---+                  |
    ^                                    |
    |                                    |
    +------------------------------------+

这很糟糕! 两个String实例都会在超出作用域时尝试释放内存缓冲区,导致重复释放错误。 你也可能创建两个指向同一内存缓冲区的不同可变&mut String引用,违反了Rust的借用规则。

案例研究2:u32

u32实现了Copy。实际上,所有整数类型都是如此。 一个整数就是内存中代表数字的那些字节。没有别的! 如果复制那些字节,就会得到另一个完全有效的整数实例。 没有任何不良后果,所以Rust允许这样做。

案例研究3:&mut u32

当我们介绍所有权和可变借用时,明确了一条规则:任何时候对一个值只能有一个可变借用。 这就是&mut u32不实现Copy的原因,即便u32本身实现了。

如果&mut u32实现了Copy,你就可以创建多个指向同一值的可变引用,并同时在多处修改它。 这将违反Rust的借用规则!因此,无论T是什么,&mut T都不实现Copy

实现Copy

大多数情况下,你不需要手动实现Copy。 你可以这样派生它:

#![allow(unused)]
fn main() {
#[derive(Copy, Clone)]
struct MyStruct {
    field: u32,
}
}

参考资料

  • 本节的练习位于 exercises/04_traits/11_copy