Rust 的智能指针为程序员提供了强大的内存管理和数据结构构建工具。通过 Box、Rc 和 Arc 等类型,可以实现所有权传递、共享所有权和多线程安全等功能。智能指针还通过 RefCell 和 Mutex 等提供了内部可变性和并发安全性。智能指针是 Rust 中重要的工具,有效地平衡了性能、安全性和灵活性,为开发者提供了可靠的解决方案。
1.Box<T>:堆上分配内存
Box<T> 允许将数据存储在堆上,并在离开作用域时自动释放。
fn main() {
let x = Box::new(5); // 使用Box将整型数据5存储在堆上,并将x指向堆上的数据
println!("x = {}", x); // 打印x的值
} // x超出作用域,Box的析构函数被调用,堆上的数据被自动释放
运行结果:
x = 5
2.Rc<T>:引用计数智能指针
Rc<T> 允许多个所有者共享相同的数据,使用引用计数进行内存管理。
use std::rc::Rc;
fn main() {
let shared_data = Rc::new(vec![1, 2, 3]); // 创建一个引用计数智能指针,用于共享一个Vec
let clone1 = Rc::clone(&shared_data); // 克隆引用计数智能指针
let clone2 = shared_data.clone(); // 也可以使用方法调用语法进行克隆
println!("{:?}", shared_data); // 打印共享数据
} // 共享数据在所有所有者离开作用域后才会被释放
运行结果:
[1, 2, 3]
3.Arc:多线程安全的引用计数智能指针
Arc 是 Rc 的多线程安全版本,使用原子操作进行引用计数,适用于多线程环境。
use std::sync::Arc;
use std::thread;
fn main() {
let shared_data = Arc::new(vec![1, 2, 3]); // 创建一个多线程安全的引用计数智能指针
let clone1 = Arc::clone(&shared_data); // 克隆智能指针
let clone2 = Arc::clone(&shared_data); // 克隆智能指针
let handle1 = thread::spawn(move || {
println!("{:?}", clone1);
});
let handle2 = thread::spawn(move || {
println!("{:?}", clone2);
});
handle1.join().unwrap(); // 等待线程1结束
handle2.join().unwrap(); // 等待线程2结束
} // 共享数据在所有所有者离开作用域后才会被释放
运行结果:
[1, 2, 3]
[1, 2, 3]
4.Cell<T>和RefCell<T>:提供内部可变性的智能指针
两个都是提供内部可变性的智能指针,但两者有一定的区别。
(1)Cell<T>
Cell<T>:不进行运行时借用规则检查,因此在不可变引用的情况下可以修改其内部值,但无法创建可变引用。Cell<T> 适用于 T 实现 Copy 的情况:
use std::cell::Cell;
fn main() {
let count = Cell::new(0); // 创建一个 Cell 包裹的可复制类型(Copy)整数
// 使用不可变引用修改内部值
count.set(1);
println!("Count: {}", count.get()); // 输出: Count: 1
// 无法创建可变引用,下面代码会导致编译错误
// let mut borrowed_count = count.get_mut();
}
运行结果:
Count: 1
(2)RefCell<T>
RefCell和Cell功能一致,不过RefCell可用于你确信代码是正确的,而编译器却发生了误判时。在 Rust 编译器的ctxt结构体中,存在着许多被RefCell类型封装的map字段。这主要源于这些map在代码中被广泛地分散使用和修改。由于这种广泛的分散使用,管理可变和不可变状态变得异常复杂,有时甚至是不可行的。这种复杂性导致编译器经常会报各种错误,给开发者带来不便。
use std::cell::RefCell;
fn main() {
let s = RefCell::new(String::from("hello, world"));
let s1 = s.borrow();
let s2 = s.borrow_mut();
println!("{},{}", s1, s2);
}
上面代码在编译期不会报任何错误,你可以顺利运行程序,但是依然会因为违背了借用规则导致了运行期 panic。可以看到RefCell 实际上并没有解决可变引用和引用可以共存的问题,只是将报错从编译期推迟到运行时。
thread 'main' panicked at main.rs:413:16:
already borrowed: BorrowMutError
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
在不可变引用的情况下创建可变引用
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]); // 创建一个 RefCell 包裹的可变 Vec
// 在不可变引用的情况下创建可变引用
{
let mut borrowed_data = data.borrow_mut();
borrowed_data.push(5); // 可以在可变引用下再次修改内部值
}
// 获取修改后的值并打印
println!("Data: {:?}", data.borrow()); // 输出: Data: [1, 2, 3, 4, 5]
}
运行结果:
Data: [1, 2, 3, 5]