Skip to content

Rust - 智能指针

在 Rust 中,值默认分配在**栈(stack)上,这种方式快速高效。然而,有时我们需要将数据分配在堆(heap)**上。这对于编译时大小未知的数据、无需昂贵复制即可转移大量数据的所有权,或者创建递归数据结构是必需的。智能指针(Smart pointers)是一种行为类似于指针但还包含额外元数据和功能的数据结构,例如管理它们指向的内存。

所有智能指针都实现了 Deref 和 Drop trait(特性),这赋予了它们超越普通 struct(结构体)的特殊能力。

Trait包 & 描述
Derefstd::ops::Deref
允许智能指针 struct 的实例像引用一样被对待。这使得 *my_box 这样的代码能够访问内部数据。
Dropstd::ops::Drop
允许你自定义当智能指针实例超出作用域时运行的代码。这是 Rust RAII(资源获取即初始化)模式的基础,确保自动内存释放和资源清理。

本章将探讨 Box<T>(最简单的智能指针),然后简要介绍其他关键指针,如 Rc<T> 和 RefCell<T>。

Box<T> 智能指针,通常简称为“box”,是在堆上分配值最直接的方式。当你使用 Box<T> 时,指针本身存储在栈上,而它指向的数据 (T) 则位于堆上。Box<T> 除了初始的堆分配外,没有其他性能开销。

  • 递归类型:对于定义中引用自身的类型,如链表。编译器需要在编译时知道类型的大小,而 Box<T> 通过将递归部分存储在堆上(指针具有已知固定大小)来打破无限递归。
  • 大数据量:为了在不复制数据的情况下转移大量数据的所有权。在栈上移动 Box<T> 的开销很小,因为只移动了指针。
  • Trait 对象:在运行时确定其类型(动态分发)的情况下使用值,这将在高级主题中介绍。

以下是如何使用 Box 在堆上存储一个 i32 类型的值。变量 b 位于栈上,并持有堆上值 5 的地址。

fn main() {
// 这个值在栈上
let stack_var: i32 = 5;
// 这个值分配在堆上
// 'b' 是栈上的一个 Box<i32>,指向堆上的数据
let b = Box::new(stack_var);
println!("b = {}", b);
// 要访问 box 内部的数据,我们必须对其进行解引用
assert_eq!(5, *b);
println!("The value inside the box is: {}", *b);
}
// 当 'b' 在 main 函数结束时超出作用域,
// Drop trait 被调用,从堆中释放 box 的内存。
b = 5
The value inside the box is: 5

为了更好地理解 Box<T>,我们来创建自己的一个简单版本,名为 MyBox<T>。这将演示 Deref 和 Drop trait 是如何工作的。

Deref trait 允许我们自定义解引用运算符 (*) 的行为。通过实现 Deref,我们可以编写 *y,让 Rust 将其转换为 *(y.deref())。

use std::ops::Deref;
// 一个名为 MyBox 的泛型元组结构体
struct MyBox<T>(T);
impl<T> MyBox<T> {
// 一个类似构造函数的功能,用于创建新实例
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
// 为 MyBox<T> 实现 Deref trait
impl<T> Deref for MyBox<T> {
// 关联类型,指定 `*` 将返回一个什么类型的引用
type Target = T;
fn deref(&self) -> &Self::Target {
// 我们返回元组结构体内部数据的引用
&self.0
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
// 在幕后,Rust 正在运行:*(y.deref())
assert_eq!(5, *y);
println!("Successfully dereferenced MyBox!");
}

Drop trait 对于 RAII 至关重要。drop 方法中的代码会在值超出作用域时自动运行。你很少需要自己实现 Drop,因为 Rust 会为标准类型处理内存释放。然而,对于管理文件句柄或网络连接等其他资源,它至关重要。

struct CustomSmartPointer {
data: String,
}
// 实现 Drop trait
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") };
let d = CustomSmartPointer { data: String::from("other stuff") };
println!("CustomSmartPointers created.");
// 在 main 函数结束时,'d' 先被丢弃,然后是 'c'(按创建的相反顺序)。
}
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!

适用于不同场景的其他智能指针

Section titled “适用于不同场景的其他智能指针”

Box<T> 用于单一所有权。Rust 的标准库为更复杂的场景提供了其他智能指针:

  • Rc<T>(引用计数): 当一个值需要有多个所有者时使用。Rc<T> 跟踪对一个值的引用数量。只有当最后一个引用超出作用域时,数据才从堆中释放。它适用于单线程场景。
  • RefCell<T>(内部可变性): 允许你在存在不可变引用的情况下修改数据。这种模式在运行时而非编译时强制执行 Rust 的借用规则。违反规则(例如,同时有两个可变借用)将导致 panic!。

结合使用 Rc<T> 和 RefCell<T> 是一种常见模式,用于创建图或其他复杂数据结构,在这些结构中,一个值由多个其他值拥有,并且可能需要被修改。