c++中就有智能指针的概念:共享指针,独占指针。顾名思义,共享指针,是多个指针可以持有同一个对象;独占指针,只能有一个指针持有对象。
c++中的共享指针存在循环引用导致对象无法释放,进而导致内存泄漏的问题,为了解决这个问题,引入了weak指针。同样的,rust中也有共享指针,也同样存在这样的问题,并且rust编译器在编译阶段无法检查出这种错误。rust的解决办法和c++中的解决办法类似,引入了weak指针的概念。
像这种纯语法类的错误,而不是业务上的bug,rust编译器也无法检查出来,个人认为rust相对于c++的进步是很有限的。内存安全,包管理是两个显而易见的优势,其他方面,并不明显;而c++的优势更加明显,在内存管理方面,如果都使用共享指针,那么内存方面的错误也会大大减少,另外c++的生态更加丰富,并且能直接调用系统调用。c++的优势太多了。
Box<T>类似于c++中的独占指针,Rc<T>类似于共享指针。Rc<T>创建的对象是不可修改的,因为Rc<T>会导致共享所有权,如果每个Rc<T>都能修改对象,那么就违背了rust的借用规则要求,即在同一个时刻,只能有一个可以修改,由此引出了RefCell<T>,这是rust独有的特点。
-
优先考虑使用
Box<T>或普通引用 -
当需要多个所有者时使用
Rc<T> -
当需要多个所有者且需要修改时使用
Rc<RefCell<T>>。专门引出RefCell来实现对象的可变性,为什么不把Rc<T>直接实现成可变的呢,这样岂不是更简洁? -
同一个作用域内只能有一个可变引用,或者多个不可变引用,可变引用和不可变引用不能同时存在。类似于读写锁。
1 Box<T>
Box<T>是可以修改的。
rust
fn main() {
// 必须声明为 mut 才能修改 Box 中的值
let mut boxed_number = Box::new(5);
println!("原始值: {}", *boxed_number);
// 修改 Box 中的值
*boxed_number = 10;
println!("修改后: {}", *boxed_number);
// 通过可变引用修改
let reference = &mut *boxed_number;
*reference = 20;
println!("再次修改: {}", boxed_number); // 会自动解引用
// 使用 deref_mut
use std::ops::DerefMut;
*boxed_number.deref_mut() = 30;
println!("通过 deref_mut 修改: {}", boxed_number);
}
2 Rc<T>
Rc<T>不可以修改。
rust
use std::rc::Rc;
fn main() {
// 创建一个字符串数据,用 Rc 包装
let data = Rc::new(String::from("Hello, Rust!"));
// 创建第一个引用计数指针
let pointer1 = Rc::clone(&data);
println!("pointer1: {}", pointer1);
println!("当前引用计数: {}", Rc::strong_count(&data));
// 创建第二个引用计数指针
let pointer2 = Rc::clone(&data);
println!("pointer2: {}", *pointer2);
println!("当前引用计数: {}", Rc::strong_count(&data));
{
// 在作用域内创建第三个引用
let pointer3 = Rc::clone(&data);
println!("pointer3: {}", pointer3);
println!("作用域内引用计数: {}", Rc::strong_count(&data));
} // pointer3 离开作用域,引用计数减1
// 作用域外,引用计数恢复
println!("作用域外引用计数: {}", Rc::strong_count(&data));
// 所有指针指向相同的数据
println!("pointer1 和 pointer2 指向相同数据: {}",
Rc::ptr_eq(&pointer1, &pointer2));
// 当所有 Rc 指针离开作用域,数据会被自动清理
// 此时引用计数变为 0
}
3 RefCell<T>
3.1局部可变性
如下代码,self是不可变的,但是可以通过self修改self内部的成员,实现了可变性穿透,也是很NB的存在。
rust
use std::cell::RefCell;
pub trait Messenger {
fn send(& self, msg: &str);
}
pub struct LimitTracker<'a, T: 'a + Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where T: Messenger {
pub fn new(messenger: & T, max: usize) -> LimitTracker<T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send("Error: you are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger.send("Warnning: you've used up over 90");
} else if percentage_of_max >= 0.75 {
self.messenger.send("Warnning: you've used up over 75");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockMessenger {
sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: RefCell::new(vec![])
}
}
}
impl Messenger for MockMessenger {
fn send(& self, message: &str) {
//如果sent_messages不声明为RefCell,那么sent_message在这里不能修改
//因为self是不可变引用
self.sent_messages.borrow_mut().push(String::from(message));
}
}
#[test]
fn it_sends_an_over_75_percent_warning_messages() {
let mock_messenger = MockMessenger::new();
let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);
limit_tracker.set_value(80);
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}
}
3.2拥有多重所有权的可变数据
rust
use std::rc::Rc;
use std::cell::RefCell;
// 定义一个简单的图节点
#[derive(Debug)]
struct Node {
value: i32,
// 使用 Rc<RefCell<Node>> 实现多重所有权
neighbors: Vec<Rc<RefCell<Node>>>,
}
impl Node {
fn new(value: i32) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Node {
value,
neighbors: Vec::new(),
}))
}
fn add_neighbor(&mut self, neighbor: Rc<RefCell<Node>>) {
self.neighbors.push(neighbor);
}
fn print_with_neighbors(&self) {
println!("Node {} has neighbors: {:?}",
self.value,
self.neighbors.iter().map(|n| n.borrow().value).collect::<Vec<_>>()
);
}
}
fn main() {
// 创建三个节点
let node1 = Node::new(1);
let node2 = Node::new(2);
let node3 = Node::new(3);
// node1 连接到 node2 和 node3
node1.borrow_mut().add_neighbor(Rc::clone(&node2));
node1.borrow_mut().add_neighbor(Rc::clone(&node3));
// node2 也连接到 node3(共享所有权)
node2.borrow_mut().add_neighbor(Rc::clone(&node3));
// 验证引用计数
println!("Reference count of node3: {}", Rc::strong_count(&node3)); // 应该是3
// 打印所有节点的邻居
node1.borrow().print_with_neighbors();
node2.borrow().print_with_neighbors();
node3.borrow().print_with_neighbors();
// 修改 node3 的值(通过任意一个引用)
node3.borrow_mut().value = 33;
// 查看修改是否生效
println!("\nAfter modifying node3:");
println!("Node3 value from node1's neighbor: {}",
node1.borrow().neighbors[1].borrow().value);
println!("Node3 value from node2's neighbor: {}",
node2.borrow().neighbors[0].borrow().value);
// 创建一个循环引用(双向连接)
node3.borrow_mut().add_neighbor(Rc::clone(&node1));
println!("\nAfter creating cycle:");
println!("Reference count of node1: {}", Rc::strong_count(&node1));
}
4循环引用
循环引用,导致无法释放:
(1)定义结构体struct User,为User实现Drop trait,当User被释放的时候,Drop trait会被调用
(2)创建两User的两个对象alice和bob,alice和bob相互引用
(3)当进程退出的时候,看不到drop函数被调用,说明alice和bob两个对象没有被释放
rust
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct User {
name: String,
friends: RefCell<Vec<Rc<User>>>,
}
// 实现 Drop trait 来创建析构函数
impl Drop for User {
fn drop(&mut self) {
println!("🚮 User '{}' 被释放了", self.name);
}
}
fn create_cycle_reference() {
println!("=== 1. 创建循环引用(内存泄漏) ===");
let alice = Rc::new(User {
name: "Alice".to_string(),
friends: RefCell::new(Vec::new()),
});
let bob = Rc::new(User {
name: "Bob".to_string(),
friends: RefCell::new(Vec::new()),
});
// 相互引用,形成循环
alice.friends.borrow_mut().push(Rc::clone(&bob));
bob.friends.borrow_mut().push(Rc::clone(&alice));
println!("引用计数 - Alice: {}, Bob: {}",
Rc::strong_count(&alice),
Rc::strong_count(&bob));
// 函数结束时,这些对象应该被释放,但由于循环引用,它们不会被释放
println!("函数结束,应该看到释放消息吗?");
}
fn main() {
create_cycle_reference();
println!("\n=== main函数继续执行 ===");
println("这里不会看到Alice和Bob的释放消息,因为它们还在内存中!");
// 手动触发垃圾回收(在Rust中,实际是离开作用域时自动清理)
println!("\n=== 程序结束 ===");
// 由于内存泄漏,程序结束时Alice和Bob仍然不会被释放
}
使用weak指针,可以看到drop函数被调用:
(1)strong_count是强引用计数,也就是几个Rc指针持有对象,weak_count是弱引用计数,也就是几个Weak指针持有对象。对象的释放不受weak_count约束,当strong_count减为0时,即使weak_count不是0,那么也会释放对象。
(2)Weak指针不能单独创建,必须依赖Rc指针而存在,通过Rc指针的downgrade来获取Weak指针;通过Weak指针的upgrade方法,可以判断对象是不是还存在。
rust
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct User {
name: String,
friends: RefCell<Vec<Weak<User>>>,
}
impl Drop for User {
fn drop(&mut self) {
println!("✅ User '{}' 被正确释放了", self.name);
}
}
fn create_without_cycle() {
println!("=== 2. 使用Weak避免循环引用 ===");
let alice = Rc::new(User {
name: "Alice".to_string(),
friends: RefCell::new(Vec::new()),
});
let bob = Rc::new(User {
name: "Bob".to_string(),
friends: RefCell::new(Vec::new()),
});
// 使用弱引用,不会形成循环
alice.friends.borrow_mut().push(Rc::downgrade(&bob));
bob.friends.borrow_mut().push(Rc::downgrade(&alice));
println!("强引用计数 - Alice: {}, Bob: {}",
Rc::strong_count(&alice),
Rc::strong_count(&bob));
// 显示弱引用数量
println!("弱引用计数 - Alice: {}, Bob: {}",
Rc::weak_count(&alice),
Rc::weak_count(&bob));
println!("函数结束,现在应该看到释放消息...");
}
fn main() {
create_without_cycle();
println!("\n=== main函数继续执行 ===");
println!("Alice和Bob已经被正确释放!");
// 创建一个临时的用户来演示生命周期
println!("\n=== 3. 演示局部作用域 ===");
{
let temp_user = Rc::new(User {
name: "临时用户".to_string(),
friends: RefCell::new(Vec::new()),
});
println!("临时用户创建,强引用计数: {}", Rc::strong_count(&temp_user));
// temp_user 在这个作用域结束时会被释放
}
println!("作用域结束,临时用户应该已被释放");
println!("\n=== 程序结束 ===");
}