昨天我们学习了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的定义如下:

1
2
3
pub trait Debug {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>;
}

了解了派生宏derive macro的知识,下面进入正题开始学习Clone, Copy, Drop这3个内存相关的trait。

Clone

std::clone::Clone trait的定义如下:

1
2
3
4
5
6
7
pub trait Clone {
    fn clone(&self) -> Self;

    fn clone_from(&mut self, source: &Self) { 
        *self = source.clone()
    }
}

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的定义如下:

1
pub 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
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 可以编译通过
#[derive(Debug, Clone, Copy)]
struct Foo {
    num: i32
}

// 可以编译通过
#[derive(Debug, Clone)]
struct Bar {
    num: i32,
    name: String,
}


// 编译错误: the trait `Copy` may not be implemented for this typerustcE0204
#[derive(Debug, Clone, Copy)] 
struct Zoo {
    num: i32,
    name: String, // String没有实现Copy, String中的vec: Vec<u8>实现了Drop trait,因此不能为Zoo标记实现Copy trait
}

可以从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的定义如下:

1
2
3
pub trait Drop {
    fn drop(&mut self);
}

实现Drop trait的类型要实现一个drop函数,当其所有者变量离开作用域时就会自动调用该方法。

大多数情况下不需要我们手动为类型实现Drop trait,系统默认会对类型中的内部类型(例如结构体的每个字段)做drop。 一般只在需要释放外部资源的场景,这些外部资源是指编译器无法得知的被使用额外资源,可以在drop实现中做释放。

Drop和Copy是互斥的

无法为类型同时实现Drop和Copy。因为Copy是栈内存的按位浅拷贝,而Drop是为了释放堆内存及额外资源的。

参考