0x00 开篇
在 Rust 中,大部分的值默认都是在 栈 上分配的。如果我想让值在堆上分配,那么最简单的方式就是使用 Box。通过将值装箱使值在堆上分配,然而获得的这个实例 box 也是一个智能指针(Smart Pointer)。本篇文章的阅读时间大约 5 分钟。
0x01 Box 是什么?
Box 属于智能指针(Smart Pointer)的一种,归根结底还是一种结构体,来看它在官方源码内的定义(/allock/boxed.rs):
pub struct Box<
T: ?Sized,
A: Allocator = Global,
>(Unique<T>, A);
接收一个在编译时可以不确定大小的 T 类型。Unique 表示对类型为 T 的值的唯一引用。也就是表示它唯一的拥有 T 类型的堆分配。来看下代码示例:
// 在堆上分配内存,然后将 20 放入。
let a: Box<i32> = Box::new(20);
0x02 Box 的特点
Box 结构体实现了Deref trait。我先来介绍下 Deref,来看官方源码的定义(core/ops/deref.rs):
#[lang = "deref"]
#[doc(alias = "*")]
#[doc(alias = "&*")]
pub trait Deref {
// 解引用后的结果类型
type Target: ?Sized;
// 获取解引用后的值
fn deref(&self) -> &Self::Target;
}
我们可以通过注解了解到,它的作用等同于 * 或者 &* (&*是是对原始指针的解引用,暂时了解即可)。当然,我们可以使用 * 操作符来对 Box 类型的指针进行解引用,也可以调用 deref 方法解引用。示例代码如下:
fn main() {
// 在堆上分配内存,然后将 20 放入。
let a: Box<i32> = Box::new(20);
// 解引用
let b = a.deref();
let c = *a;
println!("使用 deref() 解引用: {}", b);
println!("使用 * 解引用: {}", c);
}
// 运行结果:
// 使用 deref() 解引用: 20
// 使用 * 解引用: 20
0x03 智能指针有哪些?
Rust 标准库中提供了多种智能指针类型,包括我们前面介绍的 Rc<T> 和 Arc<T>,也都是智能指针。
简单列举下常用的智能指针吧:
Box<T>:用于在堆上分配内存并存储值,拥有所有权和移动语义。Rc<T>:用于多个所有者之间共享值,基于引用计数实现,支持clone操作。Arc<T>:类似于Rc<T>,但是是线程安全的,可以跨线程共享值。Cell<T>和RefCell<T>:用于在不可变类型中提供可变性,支持借用和借用检查。(后面章节会介绍)Mutex<T>和RwLock<T>:用于在线程间共享可变数据,支持同步和互斥操作。(后面高级章节会介绍)
0x04 Box 和 &T 的异同
Box<T> 类型和 &T 类型都是 Rust 中的引用类型,那他们有什么区别和共同点呢?
相同点
- 都用于引用其他类型的值,而不是直接拥有这些值的所有权;
- 都可以避免在函数调用或数据传递中产生所有权转移的问题,从而实现数据共享和避免内存泄漏;
- 都支持 Rust 的借用机制,可以用于避免数据竞争和提高代码安全性。
不同点
Box<T>类型是一个智能指针类型,用于在堆上分配内存并存储值,拥有所有权和移动语义。因此,使用Box<T>类型时需要使用*运算符或者deref()方法 对其进行解引用,才能访问其中存储的值。而&T类型是一个引用类型,用于引用其他值的地址,因此不需要进行解引用操作。Box<T>类型的大小是固定的,因为它只存储一个指针,而指针的大小是固定的。而&T类型的大小是与所引用的值的大小相关的,因为它存储的是一个指向值的指针,比如一些胖指针。Box<T>类型在创建后会分配一块新的内存,因此需要显式地释放它所拥有的资源。而&T类型只是引用其他类型的值,不需要管理内存。
应用场景
那我们如何分辨使用场景呢?当需要在堆上分配内存并拥有所有权时,可以使用 Box<T> 类型;当需要引用其他类型的值时,可以使用 &T 类型。其实更负责的场景下,还需要具体情况具体分析了。
0x05 小结
本文简单介绍了下 Box<T> 的使用,以及智能指针的类型吧。重点比较了 Box<T> 类型和 &T 类型的异同, Box<T> 的使用频率不如栈上分配的值和引用类型高,但它在某些情况下仍然是必要的。