智能指针
智能指针是一种数据结构,它们的行为类似于指针,但具有额外的元数据和功能。在 Rust 中,智能指针通常实现了 Deref
和 Drop
特质,允许它们像引用一样工作并在离开作用域时自动清理资源。在本章中,我们将探索 Rust 中的各种智能指针类型及其用途。
引用回顾
在深入智能指针之前,让我们先回顾一下 Rust 中的普通引用:
fn main() {
let x = 5;
let y = &x; // y 是 x 的引用
assert_eq!(5, x);
assert_eq!(5, *y); // 使用解引用运算符 * 访问引用的值
}
引用是 Rust 中最简单的指针类型,它们没有任何特殊功能,只是借用值而不获取所有权。
Box
Box<T>
是 Rust 中最简单的智能指针类型,它允许你将值存储在堆上而不是栈上:
fn main() {
let b = Box::new(5); // 在堆上分配值 5
println!("b = {}", b);
// 可以像使用引用一样使用 Box
assert_eq!(5, *b);
} // b 离开作用域时,它指向的堆内存会被自动释放
Box 的主要用途
1. 存储已知大小但较大的数据
当你有一个较大的数据结构,但不想在栈上分配内存时,可以使用 Box
:
struct LargeStruct {
data: [u8; 1000000], // 1MB 的数据
}
fn main() {
// 在栈上分配可能导致栈溢出
// let large_struct = LargeStruct { data: [0; 1000000] };
// 在堆上分配更安全
let large_struct = Box::new(LargeStruct { data: [0; 1000000] });
println!("结构体已创建");
}
2. 创建递归类型
递归类型的大小在编译时无法确定,因此需要使用 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))))));
// 使用模式匹配访问列表元素
let mut current = &list;
while let List::Cons(value, next) = current {
println!("值: {}", value);
current = next;
}
}
3. 特质对象
Box
可以用来创建特质对象,允许在运行时使用动态分发:
trait Draw {
fn draw(&self);
}
struct Circle {
radius: f64,
}
impl Draw for Circle {
fn draw(&self) {
println!("画一个半径为 {} 的圆", self.radius);
}
}
struct Square {
side: f64,
}
impl Draw for Square {
fn draw(&self) {
println!("画一个边长为 {} 的正方形", self.side);
}
}
fn main() {
let shapes: Vec<Box<dyn Draw>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Square { side: 2.0 }),
];
for shape in shapes {
shape.draw();
}
}
Deref 特质
Deref
特质允许自定义解引用运算符 *
的行为。智能指针通过实现 Deref
特质,使它们的行为类似于引用:
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 main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y); // 相当于 *(y.deref())
}
解引用强制转换
Rust 提供了解引用强制转换(deref coercion)功能,当将一个实现了 Deref
特质的类型的值作为参数传递给函数或方法时,如果参数类型不匹配,Rust 会自动应用 deref
方法:
fn hello(name: &str) {
println!("你好,{}!", name);
}
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m); // 解引用强制转换:&MyBox<String> -> &String -> &str
}
Drop 特质
Drop
特质允许你自定义当值离开作用域时发生的行为。这对于释放资源(如文件句柄或网络连接)非常有用:
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("释放 CustomSmartPointer,数据: `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer { data: String::from("我的数据") };
let d = CustomSmartPointer { data: String::from("其他数据") };
println!("创建了智能指针");
// c 和 d 在这里离开作用域,Rust 会自动调用它们的 drop 方法
}
提前丢弃值
有时你可能需要提前丢弃一个值。Rust 提供了 std::mem::drop
函数来实现这一点:
fn main() {
let c = CustomSmartPointer { data: String::from("提前丢弃") };
println!("创建了智能指针");
drop(c); // 手动调用 drop 函数
println!("在 main 函数结束前丢弃了智能指针");
}
Rc
Rc<T>
(引用计数,Reference Counting)允许多个所有者共享同一数据的所有权。当最后一个所有者离开作用域时,数据才会被清理:
use std::rc::Rc;
fn main() {
let a = Rc::new(5); // 创建一个引用计数的值
println!("创建 a,引用计数 = {}", Rc::strong_count(&a)); // 1
let b = Rc::clone(&a); // 增加引用计数,而不是复制数据
println!("创建 b,引用计数 = {}", Rc::strong_count(&a)); // 2
{
let c = Rc::clone(&a); // 再次增加引用计数
println!("创建 c,引用计数 = {}", Rc::strong_count(&a)); // 3
} // c 离开作用域,引用计数减少
println!("c 离开作用域后,引用计数 = {}", Rc::strong_count(&a)); // 2
// 可以通过任何一个引用访问数据
println!("a = {}, b = {}", a, b);
} // a 和 b 离开作用域,引用计数变为 0,数据被清理
Rc 的限制
Rc<T>
只能用于单线程场景,并且只提供不可变访问:
use std::rc::Rc;
fn main() {
let a = Rc::new(vec![1, 2, 3]);
let b = Rc::clone(&a);
// 错误:不能获取可变引用
// a.push(4);
println!("a = {:?}, b = {:?}", a, b);
}
RefCell 和内部可变性
内部可变性是 Rust 的一种设计模式,它允许你在拥有不可变引用的情况下修改数据。RefCell<T>
提供了内部可变性:
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
// 获取不可变引用
let a = data.borrow();
println!("a = {}", a);
// 必须先释放不可变引用,才能获取可变引用
drop(a);
// 获取可变引用并修改值
let mut b = data.borrow_mut();
*b += 1;
println!("b = {}", b);
}
借用规则在运行时检查
与普通引用不同,RefCell<T>
在运行时而不是编译时检查借用规则:
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
let a = data.borrow();
let b = data.borrow(); // 可以同时有多个不可变借用
println!("a = {}, b = {}", a, b);
// 下面的代码会在运行时 panic,因为已经有不可变借用
// let mut c = data.borrow_mut();
}
结合 Rc 和 RefCell
Rc<T>
和 RefCell<T>
经常一起使用,以提供多所有者和内部可变性:
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let data = Rc::new(RefCell::new(vec![1, 2, 3]));
let a = Rc::clone(&data);
let b = Rc::clone(&data);
// 通过 a 修改数据
a.borrow_mut().push(4);
// 通过 b 也能看到修改后的数据
println!("b = {:?}", b.borrow());
}
Weak
Weak<T>
提供了对 Rc<T>
数据的非所有权引用,不会增加强引用计数,因此不会阻止数据的清理:
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
parent: Option<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: None,
children: RefCell::new(vec![]),
});
println!("leaf 强引用计数 = {}", Rc::strong_count(&leaf)); // 1
println!("leaf 弱引用计数 = {}", Rc::weak_count(&leaf)); // 0
{
let branch = Rc::new(Node {
value: 5,
parent: None,
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
// 设置 leaf 的父节点为 branch,使用 Weak 引用避免循环引用
leaf.parent = Some(Rc::downgrade(&branch));
println!("branch 强引用计数 = {}", Rc::strong_count(&branch)); // 1
println!("branch 弱引用计数 = {}", Rc::weak_count(&branch)); // 1
println!("leaf 强引用计数 = {}", Rc::strong_count(&leaf)); // 2
println!("leaf 弱引用计数 = {}", Rc::weak_count(&leaf)); // 0
// 访问 leaf 的父节点
if let Some(parent) = &leaf.parent {
// 尝试将 Weak 引用升级为 Rc
if let Some(parent) = parent.upgrade() {
println!("leaf 的父节点是 {}", parent.value);
}
}
} // branch 离开作用域,强引用计数变为 0,数据被清理
// branch 已被清理,所以 leaf.parent 现在是悬空的 Weak 引用
println!("leaf 强引用计数 = {}", Rc::strong_count(&leaf)); // 1
// 尝试访问已清理的父节点
if let Some(parent) = &leaf.parent {
if let Some(_) = parent.upgrade() {
println!("leaf 的父节点仍然存在");
} else {
println!("leaf 的父节点已被清理");
}
}
}
Arc
Arc<T>
(原子引用计数,Atomic Reference Counting)是 Rc<T>
的线程安全版本,可以在多线程环境中安全地共享数据:
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new(vec![1, 2, 3]);
let mut handles = vec![];
for i in 0..3 {
let data_clone = Arc::clone(&data);
let handle = thread::spawn(move || {
println!("线程 {}: 数据 = {:?}", i, data_clone);
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
Mutex
Mutex<T>
(互斥锁)提供了线程安全的内部可变性,确保在任何时刻只有一个线程可以访问数据:
use std::sync::Mutex;
use std::thread;
fn main() {
let counter = Mutex::new(0);
let mut handles = vec![];
for _ in 0..10 {
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