Deref

Deref 可以访问被分配的资源。

dereference 类似于 C++ 中的指针 * 操作符,并且只能在 &... 上或者实现了 Deref trait 的类型上使用。仅引用类型的实参才会触发自动解引用

隐式 Deref 转换

Rust 编译器在碰到实参与形参的类型对不上的时候,会考虑将实参自动 Deref 以匹配函数的形参类型。考虑这样的代码:

1
2
3
4
5
6
7
8
fn main() {
let s = Box::new(String::from("hello world"));
display(&s)
}

fn display(s: &str) {
println!("{}", s);
}

s 的类型是 Box<String>,而函数的形参类型是 &str,出现对不上号的情况。于是 Rust 编译器做了如下的自动 dereference:

1
Box<String> ==> &String ==> &str

连续的 Deref 转换(引用归一化)

先前提到,Rust 只能对 & 进行 dereference,当遇到多个 &&&&&& 或者智能指针的时候,Rust 是怎么处理的呢?简而言之,可以概括为以下几点:

  1. 智能指针 Box<T> 展开成 &T(包括其他类型的智能指针,如 Rc<T>, Arc<T>
  2. 把多重 &&&&& 归一为单个 &

第二点在标准库里是这样实现的:

1
2
3
4
5
6
7
impl <T: ?Sized> Deref for &T {
type Target = T;

fn deref(&self) -> &T {
*self
}
}

DerefDerefMut

dereference 还支持将 mutable reference 转换成 immutable reference,或者 immutable reference 转换成 mutable reference.

  • T: Deref<Target=U> 时,&T 可以转换成 &U,immutable ref \to immutable ref
  • T: Deref<Target=U> 时,&mut T 可以转换成 &U
  • T: DerefMut<Target=U> 时,&mut T 可以转换成 &mut U

并且,从标准库实现上说,DerefMut 是继承了 Deref.


Drop

Drop 特征可以释放资源。在变量超出作用域的时候,执行一段特定的代码,最终编译器将帮助自动插入这段收尾代码。

当然也可以手动调用 xxx.drop() 进行手动回收。

Drop 的顺序

我们考察下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct HasDrop1;
struct HasDrop2;
impl Drop for HasDrop1 {
fn drop(&mut self) {
println!("Dropping HasDrop1!");
}
}
impl Drop for HasDrop2 {
fn drop(&mut self) {
println!("Dropping HasDrop2!");
}
}
struct HasTwoDrops {
one: HasDrop1,
two: HasDrop2,
}
impl Drop for HasTwoDrops {
fn drop(&mut self) {
println!("Dropping HasTwoDrops!");
}
}

struct Foo;

impl Drop for Foo {
fn drop(&mut self) {
println!("Dropping Foo!")
}
}

fn main() {
let _x = HasTwoDrops {
two: HasDrop2,
one: HasDrop1,
};
let _foo = Foo;
println!("Running!");
}

输出为

1
2
3
4
5
Running!
Dropping Foo!
Dropping HasTwoDrops!
Dropping HasDrop1!
Dropping HasDrop2!

由此可以得出 drop 的顺序:

  1. 变量级别,按照逆序的方式,_x_foo 之前创建,因此 _x_foo 之后被 drop
  2. 结构体内部,按照顺序的方式,结构体 _x 中的字段按照定义中的顺序依次 drop

CopyDrop 互斥

我们无法为一个类型同时实现 CopyDrop 特征。因为实现了 Copy 特征的类型会被编译器隐式的复制,因此非常难以预测析构函数执行的时间和频率。因此这些实现了 Copy 的类型无法拥有析构函数。