Box<T> 和引用 (&T/&mut T) 是 Rust 中两种不同的指针类型,它们在所有权、生命周期和用途上有显著区别。
1. 所有权区别
rust
// Box - 拥有所有权
let boxed = Box::new(5); // Box 拥有数据的所有权
let moved = boxed; // 所有权转移,boxed 不再有效
// 引用 - 借用所有权
let value = 5;
let reference = &value; // 只是借用,value 仍拥有所有权
let another_ref = reference; // 引用可以复制,所有权未转移
2. 内存布局区别
rust
let value = 42; // 栈上分配
let reference = &value; // 指针指向栈上数据
let boxed = Box::new(42); // 数据在堆上,boxed 是指向堆的智能指针
// 内存布局:
// value: 栈上,存储 42
// reference: 栈上指针 -> 指向栈上的 value
// boxed: 栈上指针 -> 指向堆上的 42
3. 生命周期区别
rust
// 引用 - 有明确的生命周期
fn get_reference<'a>(input: &'a i32) -> &'a i32 {
input // 返回引用的生命周期与输入相关
}
// Box - 拥有数据,不依赖外部生命周期
fn get_box() -> Box<i32> {
Box::new(42) // Box 的所有权被转移给调用者
}
// 常见错误示例
fn invalid_reference() -> &i32 {
let value = 42;
&value // 错误!value 将在函数结束时被销毁
}
fn valid_box() -> Box<i32> {
let value = Box::new(42);
value // 正确!Box 被移动出去
}
4. 使用场景对比
Box 的使用场景:
rust
// 1. 递归类型(大小不确定)
enum List {
Cons(i32, Box<List>), // 必须用 Box,因为 List 大小不确定
Nil,
}
// 2. 大对象(避免栈溢出)
let large_array = Box::new([0u8; 10_000_000]);
// 3. trait 对象(动态分发)
trait Animal {
fn speak(&self);
}
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Cat),
Box::new(Dog),
];
// 4. 转移所有权到堆上
fn store_on_heap(value: i32) -> Box<i32> {
Box::new(value)
}
引用的使用场景:
rust
// 1. 函数参数(只读访问)
fn calculate_length(s: &str) -> usize {
s.len()
}
// 2. 临时借用
let mut x = 5;
{
let y = &mut x; // 可变借用
*y += 1;
} // y 的借用结束
// 3. 切片引用
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..4]; // 借用数组的一部分
// 4. 遍历集合
let vec = vec![1, 2, 3];
for item in &vec { // 借用,不获取所有权
println!("{}", item);
}
5. 性能差异
rust
// 引用 - 零成本抽象
let x = 10;
let r = &x; // 只是创建指针,无额外开销
// Box - 有分配开销
let b = Box::new(10); // 1. 堆分配 2. 数据复制到堆 3. 需要最终释放
// 性能比较示例
fn process_by_ref(data: &[i32]) {
// 快速,无内存分配
for &num in data {
// 处理数据
}
}
fn process_by_box(data: Box<[i32]>) {
// 较慢,涉及堆分配和所有权转移
for &num in data.iter() {
// 处理数据
}
}
6. 可变性区别
rust
// Box 本身的可变性
let mut boxed = Box::new(5);
*boxed = 10; // 可以修改内容
let immutable_box = Box::new(5);
// *immutable_box = 10; // 错误!Box 不可变
// 引用的可变性
let mut value = 5;
let immut_ref = &value; // 不可变引用
// *immut_ref = 10; // 错误!
let mut_ref = &mut value; // 可变引用
*mut_ref = 10; // 可以修改
7. 转换关系
rust
// Box 到引用(自动解引用)
let boxed = Box::new(42);
let reference: &i32 = &*boxed; // 显式
let reference2 = &boxed; // &Box<i32>
let value = *boxed; // 解引用获取值
// 引用不会自动变成 Box
fn ref_to_box(r: &i32) -> Box<i32> {
Box::new(*r) // 需要复制数据
}
// 常见转换模式
fn process(data: &str) {
// 处理引用
}
let boxed_string = Box::new(String::from("hello"));
process(&boxed_string); // Box 自动解引用为 &str
8. 实际选择建议
使用引用当:
- 只需要临时访问数据
- 避免所有权转移
- 函数参数传递
- 性能是关键因素
- 数据生命周期清晰
rust
// 应该使用引用
fn find_max(numbers: &[i32]) -> Option<&i32> {
numbers.iter().max()
}
使用 Box 当:
- 需要拥有数据所有权
- 类型大小在编译时不确定
- 需要动态分发(trait 对象)
- 数据需要比当前作用域存活更久
- 避免大对象栈溢出
rust
// 应该使用 Box
fn create_large_data() -> Box<[u8; 1_000_000]> {
Box::new([0; 1_000_000])
}
9. 总结表格
| 特性 | Box<T> |
&T / &mut T |
|---|---|---|
| 所有权 | 拥有所有权 | 借用所有权 |
| 生命周期 | 静态确定 | 需要标注或推断 |
| 内存位置 | 数据在堆上 | 数据可在栈或堆 |
| 性能 | 有分配开销 | 通常零开销 |
| 大小 | 编译时已知 | 编译时已知 |
| 可变性 | 由 mut 关键字决定 |
由引用类型决定 |
| 主要用途 | 动态分配、递归类型 | 函数参数、临时访问 |
rust
// 最终建议:优先使用引用,必要时使用 Box
fn efficient_function(data: &[i32]) -> Box<Vec<i32>> {
// 参数用引用避免复制
// 返回用 Box 转移所有权
Box::new(data.iter().map(|x| x * 2).collect())
}