智能指针
智能指针是指除了存储数据外,还有额外元数据和功能的指针。Rust 标准库提供了多种智能指针来管理内存和实现特定功能。
指针基础
引用 vs 智能指针
fn main() {
// 普通引用:只借用数据
let x = 5;
let y = &x;
println!("引用: {}", y);
// 智能指针:拥有数据
let s = Box::new(5);
println!("Box: {}", s);
}
智能指针的特点:
- 拥有数据
- 可以有额外的元数据
- 可以自动清理资源
- 实现了
Deref和Droptrait
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> | 唯一 | 内部可变 | 是 | 多线程内部可变 |
小结
本章我们学习了:
- Box<T>:堆分配,适合递归类型和大块数据
- Rc<T>:引用计数,多所有权共享
- RefCell<T>:内部可变性,运行时借用检查
- Arc<T>:原子引用计数,线程安全共享
- Weak<T>:弱引用,避免循环引用
- Deref 和 Drop:智能指针的核心 trait
练习
- 使用
Box实现一个二叉树结构 - 使用
Rc和RefCell实现一个双向链表 - 使用
Arc和Mutex实现一个多线程计数器 - 解释为什么
Rc不能用于多线程,而Arc可以