从本节开始介绍智能指针(smart pointer),智能指针是一些数据结构,它们的行为类似于指针,但拥有额外的元数据和附加功能,引用和智能指针不同,引用是只借用数据的指针;而大多数的智能指针本身就拥有它们指向的数据,也就是具有数据的所有权。
Box
装箱类型(box)使我们可以将数据存储在堆上,并在栈中保留一个指向堆数据的指针。
使用场景
- 当拥有一个无法在编译时确定大小的类型,但又想要在一个要求固定尺寸的上下文环境中使用这个类型的值时。
- 当需要传递大量数据的所有权,但又不希望产生大量数据的复制行为时。
- 当希望拥有一个实现了指定trait的类型值,但又不关心具体的类型时。
使用装箱
let b = Box::new(5);
println!("{}", b); // 5
另外,和其他任何拥有所有权的值一样,装箱会在离开自己的作用域时被释放,包括指针和堆上的数据。
使用装箱定义递归类型
rust中使用基本类型是无法实现递归的,当然你也可以自行试一下实现一个链表数据结构,过程中必然会得到rust的报错,因为rust无法确定递归类型的大小,进而无法通过编译。
使用枚举实现链表结构
下面我们使用rust尝试实现链表,看一下rust的报错:
#[derive(Debug)]
enum List {
Cons(i32, List), // error, 递归类型有无限的大小
Nil,
}
let list = List::Cons(1,
List::Cons(2,
List::Cons(3, List::Nil)
)
);
println!("{:?}", list);
由于rust在编译过程中需要知道所有类型的大小,而在运行时递归是无限的,所以在编译过程中无法计算出递归的大小
使用Box将递归类型的大小固定下来
下面使用Box类型重新尝试看看如何实现链表:
#[derive(Debug)]
enum List {
Node(i32, Box<List>), // 使用Box标记类型
Nil,
}
let list = List::Node(1,
Box::new(List::Node(2,
Box::new(List::Node(3,
Box::new(List::Nil))
)
))
);
println!("{:?}", list);
// Node(1, Node(2, Node(3, Nil)))
因为Box是一个指针,所以Rust可以在编译时就确定一个Box的具体大小。指针的大小总是恒定的,它不会因为指向数据的大小而产生变化。
结构体实现链表结构
上面使用的枚举来实现链表,我们也可以使用结构体来实现:
#[derive(Debug)]
struct Node<T> {
value: T,
next: Box<Option<Node<T>>>
}
let list = Node {
value: 1,
next: Box::new(Some(Node {
value: 1,
next: Box::new(None)
}))
};
println!("{:?}", list);
// Node { value: 1, next: Some(Node { value: 1, next: None }) }
Box类型比较简单,所以本节内容不多,后面会介绍智能指针相关的trait,和更多智能指针的类型。