以下内容为本人学习过程中的记录笔记,其中可能存在不准确或错误,欢迎勘误及指正

简介

Box<T>智能指针的组成

在Rust的智能指针中,Box<T>是最简单的智能指针,其主要由两部分组成:(1)储存在堆内存(heap)上的数据、(2)储存在栈内存(stack)上的、指向堆内存数据的指针 ,比如下面的例子中,我们使用Box::new()在堆内存上请求了一块区域来存储数字”5”,然后返回了一个指向该区域的指针并赋值给变量”a”

1
2
3
4
5
6
7
8
9
10
fn main() {
let a = Box::new(5);
}
/* Box<T>示意图
------- ------
|stack| |heap|
------- ------
| ptr ------------>|data|
------- ------
*/

Box<T>指针的使用场景

  1. 在编译时,某类型的大小无法确定。但使用该类型时,上下文却需要知道其确切大小
  2. 当存在大量数据想要移交所有权,但需确保操作时数据不会被复制
  3. 当使用某个值时,只关心其是否满足特定的Trait约束,而不关心其具体的类型
    总体而言,Box<T>的作用主要有:(1)动态分配内存、(2)所有权转移、(3)避免内存泄漏、(4)处理递归类型、(5)实现Trait对象(见Trait对象部分)

使用Box<T>指针创建递归类型

我们首先必须了解这样一个规定:Rust在编译时,必须知道一个类型所占用的空间大小 ,而递归类型(如链表)因为递归层数的不确定,其大小在编译时也是无法确定的,比如下面的代码就无法通过编译,因为List所占用的空间大小在编译时没法确定

1
2
3
4
5
6
7
8
9
10
use crate::List::{Cons, Nil};

enum List {
Cons(i32, List),
Nil,
}

fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));
}

但是,我们知道在Rust中指针变量的大小是可以确定的,在这里就可以使用Box<T>来定义递归类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use crate::List::{Cons, Nil};

enum List {
Cons(i32, Box<List>),
Nil,
}

fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
let mut current = &list;
// 遍历list
loop {
match current {
Cons(value, next) => {
println!("{}", value);
current = next;
}
Nil => break,
}
}
}

两个基本trait

Deref trait

  • Deref trait的作用:Deref trait可以让用户自定义解引用运算符*的行为,即通过实现Deref trait,用户可以像使用常规引用一样使用智能指针

Box<T>的解引用

1
2
3
4
5
6
7
8
fn main() {
// 常规引用的解引用
let x = &5;
assert_eq!(5, *x);
// Box<T>的解引用
let y = Box::new(3);
assert_eq!(3, *y);
}

自定义Box<T>指针

在Rust中,Box<T>被定义为拥有一个元素的元组结构体(tuple struct)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}

// 为MyBox实现Deref trait让我们可以使用解引用运算符
impl<T> Deref for MyBox<T> {
type Target = T;
// 实现Deref trait中的deref方法
fn deref(&self) -> &Self::Target {
&self.0
}
}

fn main() {
let x = MyBox::new(5);
assert_eq!(5, *x); // 这里"*x"会被隐式的展开为"*(x.deref())"
}

隐式解引用转化(Deref Coercion)

隐式解引用转化(Deref Coercion)是为函数和方法提供的一种便捷特性 ,具体来说就是:如果一个类型T实现了Deref trait,那么在编译时,编译器就会将T的引用隐式地转换为经过Deref操作后生成的引用

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
use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}

impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}

fn main() {
let x = MyBox::new("hello, world!".to_string());
// 注意:向hello()传参时会发生隐式解引用转化
hello(&x);
}

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

在上面的代码中,变量x经过了两次Deref转换,即:&Mybox<String> -> &String -> &str(注:在标准库中String实现了Deref trait,所以它会隐式地转换为&str)

Drop trait

  • Drop trait的作用:实现Drop trait,可以让用户自定义当值将要离开作用域时发生的动作,比如文件或其他资源的释放等,它有点像Python中的__del__方法

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
struct CustomSmartPointer {
data: String,
}

// 为CustomSmartPointer实现Drop trait
impl Drop for CustomSmartPointer {
// 实现drop()方法
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}

fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");
}

/*
OutPut:
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!
*/

通过输出信息可以知道:程序运行完成后,内存中变量释放的顺序是从下往上的

手动释放内存

在Rust中,我们并不能直接调用某类型的drop()方法,但一般也不需要手动调用drop()方法,因为Drop trait的功能是自动运行变量被释放后的一系列行为,但有时我们又确实存在手动释放内存的需求,这时我们可以使用标准库提供的std::mem::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
use std::mem::drop;

struct CustomSmartPointer {
data: String,
}

impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}

fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
// 手动释放变量
drop(c);
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");
}

/*
OutPut:
Dropping CustomSmartPointer with data `my stuff`!
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
*/

可以发现打印的信息顺序发生了变化,这是因为我们先显式地释放了变量cc在释放时触发了c.drop()方法,所以最后的结果表现不一样

其他智能指针

Box<T>外,Rust中还存在其他的智能指针,如Rc<T>Arc<T>Mutex<T>等等,这些就等用到的时候再写吧…