Day 21: 智能指针:Box
在前面几天的学习中,我们已经掌握了Rust的所有权、借用和生命周期等核心概念。今天我们来学习Rust中另一种重要的类型——智能指针。智能指针是一类数据结构,它们不仅拥有数据的所有权,还提供了额外的元数据和功能。Box是最基础也是最常用的智能指针。
Rust的智能指针与C++的智能指针类似,但Rust的所有权模型使得智能指针的设计更加安全、清晰。Box是”堆上分配”(heap allocation)的桥梁,让我们能够在堆上存储数据,而不是栈上。
2. Box 基础
Section titled “2. Box 基础”Box是最简单的智能指针,它将数据存储在堆上,而栈上只保留一个指向堆内存的指针。
fn main() { let b = Box::new(5); println!("Box中的值: {}", b);
// 解引用访问 let b2 = Box::new(String::from("Hello, Box!")); println!("字符串Box: {}", b2);
// Box<T>实现了Deref trait,所以可以使用*解引用 let x = 5; let y = Box::new(5); println!("x == *y: {}", x == *y);}Box::new()函数会分配堆内存并存储值。当Box被drop时(离开作用域),堆上的数据会自动释放,这就是”智能”之处——我们不需要手动调用free或delete。
3. 递归类型
Section titled “3. 递归类型”Rust编译器需要在编译时确定每个类型的大小。对于递归类型(自身包含自身引用的类型),直接定义可能会导致编译器无法确定大小。
考虑一个链表节点的定义:
// 错误:Enum无法确定大小enum List { Cons(i32, List), // 这会导致编译错误 Nil,}使用Box可以解决这个问题,因为Box的大小是固定的(就是一个指针的大小):
enum List { Cons(i32, Box<List>), Nil,}
fn main() { let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Cons(3, Box::new(List::Nil)))))); println!("链表长度计算完成");}Box将递归类型的大小从”无限”变为”固定”,因为Box在任何情况下都只占用一个指针的空间。
4. dyn Trait与trait对象
Section titled “4. dyn Trait与trait对象”在Rust中,trait定义了行为接口。有时候我们需要在一系列不同类型上使用同一trait,这时就需要trait对象(trait object)。
trait对象是通过dyn Trait语法创建的,它代表任何实现了指定trait的类型。trait对象是运行时多态的,而泛型和monomorphization是编译时多态。
trait Draw { fn draw(&self);}
struct Circle { radius: f64,}
struct Square { side: f64,}
impl Draw for Circle { fn draw(&self) { println!("绘制圆形,半径: {}", self.radius); }}
impl Draw for Square { fn draw(&self) { println!("绘制正方形,边长: {}", self.side); }}
fn main() { // 静态分发:泛型方式 fn draw_static<T: Draw>(shape: &T) { shape.draw(); }
// 动态分发:trait对象方式 fn draw_dynamic(shape: &dyn Draw) { shape.draw(); }
let circle = Circle { radius: 5.0 }; let square = Square { side: 4.0 };
draw_static(&circle); draw_static(&square); draw_dynamic(&circle); draw_dynamic(&square);}dyn Trait返回的是胖指针(fat pointer),包含两个指针:指向数据的指针和指向vtable(虚函数表)的指针。vtable存储了trait方法的实现地址。
5. Box 和 dyn Trait 组合使用
Section titled “5. Box 和 dyn Trait 组合使用”最常见的模式是将Box和dyn Trait组合使用,这样就能创建异构集合——同一个集合中可以存放不同类型的对象,只要它们实现了相同的trait。
trait Animal { fn speak(&self);}
struct Dog;struct Cat;struct Duck;
impl Animal for Dog { fn speak(&self) { println!("汪!"); }}
impl Animal for Cat { fn speak(&self) { println!("喵~"); }}
impl Animal for Duck { fn speak(&self) { println!("嘎嘎!"); }}
fn main() { // 创建动物集合 let animals: Vec<Box<dyn Animal>> = vec![ Box::new(Dog), Box::new(Cat), Box::new(Duck), ];
for animal in &animals { animal.speak(); }}这段代码展示了Rust中运行时多态的强大能力。我们可以在一个Vec中存储不同类型的动物,只要它们都实现了Animal trait。
6. Box的其他用途
Section titled “6. Box的其他用途”除了上述场景,Box还有以下重要用途:
fn main() { // 1. 函数返回 trait 对象 fn create_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x * 2) }
let closure = create_closure(); println!("闭包结果: {}", closure(5));
// 2. 避免栈溢出:对于大型数据结构,可以放在堆上 let large_array = Box::new([0u8; 10000]);
// 3. Box实现了Sized,所以可以作为函数参数 fn process_box(b: Box<[u8]>) { println!("处理了 {} 字节", b.len()); } process_box(large_array);}注意Box和Box的区别:前者是切片trait对象,大小在运行时确定。
今天我们学习了Box智能指针及其应用场景。Box的核心价值在于:
- 在堆上分配数据,适合大型数据结构
- 解决递归类型的大小问题
- 支持动态分发(dyn Trait)
Box是Rust智能指针家族中最基础的一员,后续我们还会学习Rc、RefCell、Arc等更复杂的智能指针,它们各自解决了不同场景下的所有权问题。