rust语言基础学习: 内存相关的3个trait, Clone, Copy, Drop
2020-07-23
昨天我们学习了AsRef和AsMut两个trait,使用这两个trait可以实现引用到引用的转换:
- 如果类型
U
实现了AsRef<T>
,则as_ref可以实现&U
到&T
的转换 - 如果类型
U
实现了AsMut<T>
,则as_ref可以实现从&U
到&mut T
的转换 - 如果
T
实现了AsRef<U>
,那么&T
就实现了AsRef<U>
- 如果
T
实现了AsRef<U>
,那么&mut T
就实现了AsRef<U>
- 如果
T
实现了AsMut<U>
,那么&mut T
就实现了AsMut<U>
到目前为止我们已经学习了Rust标准库中的11个常用的trait:
- std::str::FromStr
- std::convert::From
- std::convert::TryFrom
- std::convert::Into
- std::convert::TryInto
- std::ops::Deref
- std::ops::DerefMut
- std::default::Default
- std::convert:AsRef
- std::convert:AsMut
- std::marker::Sized
今天将要学习内存相关的3个trait: Clone, Copy, Drop。
在Rust中可以通过trait来约定类型的行为,Rust还提供了派生宏(derive macro),可以生成一些trait的实现。
例如#[derive(Debug)]
可以为数据类型实现Debug trait,为类型增加了debug能力,这样就可以使用{:?}
用println!
格式化打印类型的数据。Debug trait的定义如下:
1pub trait Debug {
2 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>;
3}
了解了派生宏derive macro的知识,下面进入正题开始学习Clone, Copy, Drop这3个内存相关的trait。
Clone #
std::clone::Clone trait的定义如下:
1pub trait Clone {
2 fn clone(&self) -> Self;
3
4 fn clone_from(&mut self, source: &Self) {
5 *self = source.clone()
6 }
7}
Clone trait有clone()
和clone_from()
两个方法,其中clone_from()具有默认实现,所以一般只需要我们实现clone()方法即可。
从clone_from()的默认实现可以看出,a.clone_from(&b)
在功能上等同于a = b.clone()
,但是如果a已经存在的话,使用a.clone_from(&b)
就可以重用a的内存,避免了重新进行内存分配,因此效率也高一些。
如果类型中包含的其他类型都实现了Clone trait,就可以通过derive macro #[derive(Clone)]
为类型自动实现Clone trait。
例如一个结构体的所有字段都实现了Clone trait,就可以使用#[derive(Clone)]
。
clone操作是深度拷贝,栈内存和堆内存将会被一起拷贝。因为是深度拷贝,因此这个拷贝操作耗费可能是昂贵的,也可能是廉价的。在Rust中的clone操作是显式的,需要显式调用。
Copy #
std::marker::Copy trait的定义如下:
1pub trait Copy: Clone { }
在学习AsRef和AsMut trait时,我们学习了一个标记trait Sized。 Copy trait也是一个标记trait。从Copy trait的定义看,如果一个类型要实现Copy trait,必须实现Clone trait。
如果类型中包含的其他类型都实现了Copy trait,就可以通过derive macro #[derive(Copy)]
为类型自动实现Copy trait
还记得前面学习所有权, Move和Copy语义时。如果一个类型实现了Copy trait,那么该类型赋值、传参、函数返回就会使用Copy语义,对应的值会被按位浅拷贝,产生新的值。
Rust不允许自身或其任何部分实现了Drop trait的类型使用Copy trait。
例1:
1// 可以编译通过
2#[derive(Debug, Clone, Copy)]
3struct Foo {
4 num: i32
5}
6
7// 可以编译通过
8#[derive(Debug, Clone)]
9struct Bar {
10 num: i32,
11 name: String,
12}
13
14
15// 编译错误: the trait `Copy` may not be implemented for this typerustcE0204
16#[derive(Debug, Clone, Copy)]
17struct Zoo {
18 num: i32,
19 name: String, // String没有实现Copy, String中的vec: Vec<u8>实现了Drop trait,因此不能为Zoo标记实现Copy trait
20}
可以从Copy trait的文档查看哪些类型实现了Copy trait,整理归纳如下:
- 所有的整形类型,例如i32
- 布尔类型bool
- 所有的浮点类型,例如f64
- 字符类型char
- 元组,当且仅当其包含的类型也都实现了Copy trait,例如(i32, i32)实现了Copy,但(i32, String)就没有
- 数组,当且仅当其内部元素类型实现了Copy trait
- ……
Copy和Clone的区别 #
Copy | Clone | |
---|---|---|
trait定义 | Copy继承自Clone,是Copy的类型一定是Clone的类型 | 是Clone的类型不一定是Copy的类型 |
复制特点 | Copy仅对栈内存做按位复制 | Clone是深度拷贝,栈和堆都可以是Clone的目标 |
使用方式 | Copy是给编译器用的,告诉编译器这个是Copy的类型使用Copy语义,而不是Move语义。Copy语义在传参、赋值、函数返回值是自动触发。 | Clone是给程序员使用的,必须手动调用clone |
使用限制 | 1.类型必须实现了Clone 2.类型内部的其他类型必须都实现了Copy 3.不允许自身或其任何部分实现了Drop trait的类型使用Copy | 类型内部的其他类型必须都实现了Clone |
耗费代价 | 廉价 | 可能是昂贵的,也可能是廉价的 |
Drop #
std::ops::Drop trait的定义如下:
1pub trait Drop {
2 fn drop(&mut self);
3}
实现Drop trait的类型要实现一个drop函数,当其所有者变量离开作用域时就会自动调用该方法。
大多数情况下不需要我们手动为类型实现Drop trait,系统默认会对类型中的内部类型(例如结构体的每个字段)做drop。 一般只在需要释放外部资源的场景,这些外部资源是指编译器无法得知的被使用额外资源,可以在drop实现中做释放。
Drop和Copy是互斥的 #
无法为类型同时实现Drop和Copy。因为Copy是栈内存的按位浅拷贝,而Drop是为了释放堆内存及额外资源的。