30、Copy trait
复制值(第二部分)
与上一节的例子一样,但是有点不同,使用u32
而不是String
作为类型。
fn consumer(s: u32) { /* */ }
fn example() {
let s: u32 = 5;
consumer(s);
let t = s + 1;
}
它的编译不会发生错误,String
和u32
之间是存在差别的,这个差别让u32
不需要Clone就可以工作。
Copy
Copy是Rust标准库中定义的另外一种trait。
pub trait Copy: Clone { }
这是一个marker trait
(标记trait),就像Sized
一样。
如果一个类型实现了Copy
,就不需要调用.clone()
来创建该类型的新实例了,Rust会隐式地完成这个操作。
u32
就是一个实现Copy地一个类型,这就是上面的编译没有错误地原因。当调用consumer(s)
时,Rust通过执行s的bitwise copy
按位复制创建了一个新的u32
实例。
这一切都是隐式执行的,我们不需要做任何操作。
什么可以实现Copy
?
Copy
不等同于自动Clone,尽管它看起来就是这么一回事。Copy
的重点在于:类型必须满足一些条件才能实现Copy
。
首先,它必须实现Clone
,因为Copy
是Clone
的一个subtrait
(子trait)。这是有理有据的:如果Rust可以隐式的创建一个类型的新实例,那么它也能够通过调用.clone()
来显式的创建一个新实例。
更确切的说,要想实现Copy
,必须满足以下条件:
- 该类型部管理任何超出了其在内存中占用的
std::mem::size_of
字节以外的其他资源(例如:堆内存、文件句柄等等)。 - 该类型不是可变引用(
&mut T
)。
如果上面这两个条件都满足,那么Rust可以通过对原始实例执行按位复制来安全的创建该类型的新实例——这经常被称为memcpy
操作,与执行按位复制的C标准库的函数同名。
示例一、String
String是一种不能实现Copy
的类型。
这是因为String管理一个额外的资源:存储字符串数据的堆分配的内存缓冲区。
假设Rust允许为String
实现Copy
。
那么通过对原始实例进行按位复制的操作创建新的String实例的时候,原始实例和新的实例都将指向相同的内存缓冲区。
这个可不行,当两个String超出范围时,它们都会尝试释放内存缓冲区,从而导致double-free
错误。同样的,创建两个指向同一内存缓冲区的不同的&mut String
引用,也会违反Rust的借用规则导致错误。
示例二、u32
不仅是u32,所有的整数类型都实现了Copy。
integer
只是内存中表示数字的字节而已。如果我们复制这些字节,我们可以得到另一个完全有效的integer
实例,不会发生什么问题,所以Rust允许这么做。
示例三、&mut u32
当我们引入所有权和可变借用时,有一条规则我们很清楚:在任何给定的时间里,一个值只能有一个可变借用。
这就是为什么&mut u32
没有实现Copy
,尽管u32
实现了Copy。
如果&mut u32
实现了Copy,我们就可以在相同时间为同一个值创建多个可变引用,并在多个地方修改它,这就违反了Rust的借用规则!所以,无论T
是什么,&mut T
永远不会实现Copy。
实现Copy
在多数情况下,我们不需要手动实现Copy,我们还是可以使用派生宏:
#[derive(Copy, Clone)]
struct MyStruct {
field: u32,
}
练习
// TODO: implement the necessary traits to make the test compile and pass.
/* TODO */
pub struct WrappingU32 {
value: u32,
}
impl WrappingU32 {
pub fn new(value: u32) -> Self {
Self { value }
}
}
/* TODO */
这道题让我们实现Copy trait,不过我看test考察的东西不是单纯的Copy trait啊。代码就得那样写,我都不知道因为什么过不去的,后面再来认真看看(
// TODO: implement the necessary traits to make the test compile and pass.
#[derive(Clone,Copy,PartialEq,Debug)]
pub struct WrappingU32 {
value: u32,
}
impl WrappingU32 {
pub fn new(value: u32) -> Self {
Self { value }
}
}
impl std::ops::Add for WrappingU32 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::new(self.value.wrapping_add(rhs.value))
}
}
这一节的学习就先到这了…