跳到主要内容

智能指针

智能指针是指除了存储数据外,还有额外元数据和功能的指针。Rust 标准库提供了多种智能指针来管理内存和实现特定功能。

指针基础

引用 vs 智能指针

fn main() {
// 普通引用:只借用数据
let x = 5;
let y = &x;
println!("引用: {}", y);

// 智能指针:拥有数据
let s = Box::new(5);
println!("Box: {}", s);
}

智能指针的特点

  • 拥有数据
  • 可以有额外的元数据
  • 可以自动清理资源
  • 实现了 DerefDrop trait

Box<T>

Box<T> 将数据存储在堆上,栈上只存储指针。

基本用法

fn main() {
// 在堆上存储值
let b = Box::new(5);
println!("b = {}", b);

// 离开作用域时自动清理
}

用途一:编译时大小未知

// 错误:递归类型大小未知
// enum List {
// Cons(i32, List),
// Nil,
// }

// 正确:使用 Box
enum List {
Cons(i32, Box<List>),
Nil,
}

use List::{Cons, Nil};

fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));

// 遍历列表
fn print_list(list: &List) {
match list {
Cons(value, next) => {
print!("{} ", value);
print_list(next);
}
Nil => println!("结束"),
}
}

print_list(&list);
}

用途二:转移大量数据所有权

fn main() {
// 大量数据在堆上,转移时只移动指针
let large_data = Box::new([0; 1000]);

let moved_data = large_data; // 只移动指针
// large_data 不再有效
}

用途三:Trait 对象

trait Animal {
fn name(&self) -> &'static str;
fn talk(&self);
}

struct Dog;
struct Cat;

impl Animal for Dog {
fn name(&self) -> &'static str { "狗" }
fn talk(&self) { println!("汪汪!"); }
}

impl Animal for Cat {
fn name(&self) -> &'static str { "猫" }
fn talk(&self) { println!("喵喵!"); }
}

fn main() {
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog),
Box::new(Cat),
];

for animal in animals.iter() {
println!("{}说:", animal.name());
animal.talk();
}
}

Rc<T>

Rc<T>(Reference Counting)是引用计数智能指针,允许多个所有者共享同一数据。

基本用法

use std::rc::Rc;

fn main() {
// 创建 Rc
let data = Rc::new(5);

// 克隆增加引用计数
let a = Rc::clone(&data);
let b = Rc::clone(&data);

println!("引用计数: {}", Rc::strong_count(&data)); // 3
println!("值: {}", data);
}

共享数据

use std::rc::Rc;

enum List {
Cons(i32, Rc<List>),
Nil,
}

use List::{Cons, Nil};

fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));

println!("创建 a 后,引用计数 = {}", Rc::strong_count(&a));

// b 和 c 共享 a 的一部分
let b = Cons(3, Rc::clone(&a));
println!("创建 b 后,引用计数 = {}", Rc::strong_count(&a));

{
let c = Cons(4, Rc::clone(&a));
println!("创建 c 后,引用计数 = {}", Rc::strong_count(&a));
}

println!("c 离开作用域后,引用计数 = {}", Rc::strong_count(&a));
}

注意事项

use std::rc::Rc;

fn main() {
let data = Rc::new(vec![1, 2, 3]);

// Rc 只能用于单线程
// 以下代码在多线程环境会编译失败
// use std::thread;
// let data_clone = Rc::clone(&data);
// thread::spawn(move || {
// println!("{:?}", data_clone);
// });

// Rc 是不可变的
// data.push(4); // 错误!
}

RefCell<T>

RefCell<T> 提供内部可变性,允许在不可变引用的情况下修改内部数据。

基本用法

use std::cell::RefCell;

fn main() {
// 创建 RefCell
let data = RefCell::new(5);

// 借用修改
*data.borrow_mut() += 1;

println!("值: {}", data.borrow());
}

内部可变性模式

use std::cell::RefCell;

struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}

impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![]),
}
}

fn send(&self, message: &str) {
// 即使 self 是不可变引用,也能修改内部数据
self.sent_messages.borrow_mut().push(String::from(message));
}

fn message_count(&self) -> usize {
self.sent_messages.borrow().len()
}
}

fn main() {
let messenger = MockMessenger::new();

messenger.send("第一条消息");
messenger.send("第二条消息");

println!("发送消息数: {}", messenger.message_count());
}

借用规则检查

use std::cell::RefCell;

fn main() {
let data = RefCell::new(5);

// 运行时检查借用规则
let borrow1 = data.borrow();
let borrow2 = data.borrow(); // 多个不可变借用:OK

println!("{} {}", borrow1, borrow2);
drop(borrow1);
drop(borrow2);

// 可变借用
let mut borrow_mut = data.borrow_mut();
*borrow_mut = 10;
drop(borrow_mut);

// 运行时错误:同时存在可变和不可变借用
// let r1 = data.borrow();
// let r2 = data.borrow_mut(); // panic!
}

结合 Rc 和 RefCell

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Node {
value: i32,
children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
let leaf = Rc::new(Node {
value: 3,
children: RefCell::new(vec![]),
});

let branch = Rc::new(Node {
value: 5,
children: RefCell::new(vec![Rc::clone(&leaf)]),
});

println!("叶子: {:?}", leaf);
println!("分支: {:?}", branch);
}

Arc<T>

Arc<T>(Atomic Reference Counting)是线程安全的引用计数智能指针。

基本用法

use std::sync::Arc;
use std::thread;

fn main() {
let data = Arc::new(vec![1, 2, 3, 4, 5]);
let mut handles = vec![];

for _ in 0..3 {
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("线程看到的数据: {:?}", data_clone);
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap();
}

println!("引用计数: {}", Arc::strong_count(&data));
}

Arc 与 Mutex 配合

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];

for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap();
}

println!("最终计数: {}", *counter.lock().unwrap());
}

Weak<T>

Weak<T> 是弱引用,不增加引用计数,用于避免循环引用。

循环引用问题

use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});

let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});

// leaf 的 parent 指向 branch(弱引用)
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);

// 访问 parent
println!("leaf 的 parent: {:?}", leaf.parent.borrow().upgrade());

println!("branch 的引用计数: {}", Rc::strong_count(&branch));
println!("leaf 的引用计数: {}", Rc::strong_count(&leaf));
}

Cow<T>

Cow<T>(Copy on Write)延迟克隆操作。

use std::borrow::Cow;

fn main() {
let s = "hello";

// 不需要修改时,借用
let cow1: Cow<str> = Cow::Borrowed(s);
println!("借用: {}", cow1);

// 需要修改时,复制
let mut cow2: Cow<str> = Cow::Borrowed(s);
cow2.to_mut().push_str(" world");
println!("修改后: {}", cow2);
}

// 实际应用
fn process_string(input: &str) -> Cow<str> {
if input.contains("bad") {
// 需要修改时才分配新内存
let mut output = input.to_string();
output = output.replace("bad", "good");
Cow::Owned(output)
} else {
// 不需要修改时直接借用
Cow::Borrowed(input)
}
}

Deref 和 Drop

Deref Trait

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}

impl<T> Deref for MyBox<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.0
}
}

fn hello(name: &str) {
println!("你好, {}!", name);
}

fn main() {
let m = MyBox::new(String::from("Rust"));

// 自动解引用强制转换
hello(&m); // &MyBox<String> -> &String -> &str

// 也可以直接解引用
println!("{}", *m);
}

Drop Trait

struct CustomPointer {
data: String,
}

impl Drop for CustomPointer {
fn drop(&mut self) {
println!("释放 CustomPointer: {}", self.data);
}
}

fn main() {
let c1 = CustomPointer { data: String::from("第一个") };
{
let c2 = CustomPointer { data: String::from("第二个") };
println!("内部作用域结束");
} // c2 被释放
println!("外部作用域结束");
} // c1 被释放

// 提前释放
fn early_drop() {
let c = CustomPointer { data: String::from("提前释放") };
println!("使用前");
drop(c); // 显式调用 drop
println!("使用后");
}

智能指针选择指南

智能指针所有权可变性线程安全用途
Box<T>唯一可变堆分配、递归类型
Rc<T>共享不可变单线程共享
Arc<T>共享不可变多线程共享
RefCell<T>唯一内部可变单线程内部可变
Mutex<T>唯一内部可变多线程内部可变

小结

本章我们学习了:

  1. Box<T>:堆分配,适合递归类型和大块数据
  2. Rc<T>:引用计数,多所有权共享
  3. RefCell<T>:内部可变性,运行时借用检查
  4. Arc<T>:原子引用计数,线程安全共享
  5. Weak<T>:弱引用,避免循环引用
  6. Deref 和 Drop:智能指针的核心 trait

练习

  1. 使用 Box 实现一个二叉树结构
  2. 使用 RcRefCell 实现一个双向链表
  3. 使用 ArcMutex 实现一个多线程计数器
  4. 解释为什么 Rc 不能用于多线程,而 Arc 可以