Rust 中级教程 第20课——Box

249 阅读4分钟

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> 的使用频率不如栈上分配的值和引用类型高,但它在某些情况下仍然是必要的。