Rust进阶[part4]_智能指针2

Rust进阶[part4]_智能指针2

Rc<T>

Rc<T>(Reference Counted)是 Rust 标准库提供的单线程共享所有权智能指针,通过引用计数实现多所有权管理。以下是其核心特性与使用场景

核心特性

  1. 引用计数

    • 每个实例在堆上存储:

    • 实际数据 T

    • 引用计数器(记录活跃引用数量)

    • 克隆时只增加计数器,不会深拷贝数据:

      let a = Rc::new(vec![1,2,3]);
      let b = a.clone(); // 计数器从1→2
    • 最后一个引用离开作用域时释放内存

  2. 不可变共享

    • 所有者只能通过 .clone() 共享不可变引用

    • 需配合RefCell<T>实现内部可变性:

      let cell = Rc::new(RefCell::new(5));
      *cell.borrow_mut() += 1;

使用场景

下面这个例子使用box可以实现链表的结构

enum list {
    Cons(i32, Box<list>),
    Nil,
}
fn main() {
    let list = list::Cons(1, Box::new(list::Cons(2, Box::new(list::Nil))));
    println!("{:?}", list);
}
------
Cons(1, Cons(2, Nil))
  • Cons变体:表示链表节点

    第一个参数i32:存储当前节点的数值

    第二个参数Box:指向下一个节点的堆内存指针

  • Nil变体:表示链表终止节点(空节点)

如果需要共享引用,可以转换为Rc, 需要使用use std::rc::Rc;

use std::rc::Rc;

#[derive(Debug)]
enum list {
    Cons(i32, Rc<list>),
    Nil,
}

fn main() {
    let a = Rc::new(list::Cons(5, Rc::new(list::Nil)));
    let b = list::Cons(10, Rc::clone(&a));
    let c = list::Cons(15, Rc::clone(&a));

    // 打印验证
    match b {
        list::Cons(val, ref next) => {
            println!("当前值: {}", val);
            println!("下一个节点: {:?}", next);
        }
        list::Nil => println!("空节点"),
    }
}

推荐使用场景

  • 共享子结构:如链表尾部共享、树结构的子节点
  • 缓存实现:多个地方需要访问相同缓存数据
  • 事件系统:多个监听者订阅同一事件源
  • 配置共享:全局只读配置的分发

RefCell<T> 实现内部可变

RefCell<T>实现"内部可变性"的智能指针,它本身不是可变引用,但可以通过其方法获取对内部数据的可变访问权限。与 Rc<T> 结合使用时,可以实现 多所有权下的共享可变状态。以下是详细解释:

  1. 内部可变性模式
    • 允许在不可变引用(&self)下修改内部数据
    • 通过运行时借用检查替代编译时检查
    • 违反借用规则会触发 panic(而非编译错误)
  2. 关键方法
    • borrow():获取不可变借用(Ref<T>
    • borrow_mut():获取可变借用(RefMut<T>
    • into_inner():提取内部数据
  3. Cell<T> 的区别
    • Cell<T>:适用于复制类型(如 i32bool),通过 get()set() 直接操作值
    • RefCell<T>:适用于引用类型(如 Vec<T>、自定义结构体),通过借用获取引用

Rc<T>组合使用

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

let shared_data = Rc::new(RefCell::new(vec![1, 2, 3]));

// 克隆 Rc 获取共享所有权
let data1 = Rc::clone(&shared_data);
let data2 = Rc::clone(&shared_data);

// 通过 borrow_mut() 修改数据
data1.borrow_mut().push(4);
data2.borrow_mut().push(5);

// 最终结果:[1, 2, 3, 4, 5]
println!("{:?}", shared_data.borrow());

Weak<T>

Weak<T> 是 Rust 中用于打破 循环引用 的弱引用智能指针。它与 Rc<T> 配合使用,既能共享数据访问权,又不会增加引用计数,从而避免内存泄漏。

核心作用

  1. 打破循环引用
当两个 `Rc<T>` 相互引用时,会形成循环引用,导致引用计数永远不为 0,内存无法释放。`Weak<T>` 通过不增加引用计数,打破循环链。
  1. 延迟访问共享数据

Weak<T> 可以通过 .upgrade() 方法尝试获取 Option<Rc<T>>
如果数据已被释放,返回 None,避免悬空指针

  1. 实现缓存和观察者模式

缓存系统:避免缓存项因强引用无法回收

使用场景

双向链表循环引用导致无法释放内存

/**
 * 双向链表
 */
#[derive(Debug)]
struct Node {
    value: i32,
    prev: Option<Rc<RefCell<Node>>>,
    next: Option<Rc<RefCell<Node>>>,
}

#[test]
fn weak_reference() {
    let a: Rc<RefCell<Node>> = Rc::new(RefCell::new(Node {
        value: 1,
        prev: None,
        next: None,
    }));
    let b: Rc<RefCell<Node>> = Rc::new(RefCell::new(Node {
        value: 2,
        prev: Some(a.clone()),
        next: None,
    }));
    a.borrow_mut().next = Some(b.clone());

    // 循环引用:a.next -> b,b.prev -> a
    // 无法释放内存!
}

通过weak<T>解决

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

#[derive(Debug)]
struct Node {
    value: i32,
    prev: Option<Weak<RefCell<Node>>>,
    next: Option<Rc<RefCell<Node>>>,
}

fn main() {
    // 创建节点 A
    let a = Rc::new(RefCell::new(Node {
        value: 1,
        prev: None,
        next: None,
    }));

    // 创建节点 B
    let b = Rc::new(RefCell::new(Node {
        value: 2,
        prev: Some(Rc::downgrade(&a)), // 使用 Weak<T>
        next: None,
    }));

    // 设置 A 的 next
    a.borrow_mut().next = Some(b.clone());

    // 验证引用关系
    if let Some(next_node) = &a.borrow().next {
        println!("A.next.value = {}", next_node.borrow().value); // 输出 2
    }

    if let Some(prev_weak) = &b.borrow().prev {
        if let Some(prev_strong) = prev_weak.upgrade() {
            println!("B.prev.value = {}", prev_strong.borrow().value); // 输出 1
        } else {
            println!("B.prev 已释放");
        }
    }
}

✅ 推荐使用场景

  1. 双向链表:将 prev 设为 Weak<T>
  2. 树结构:子节点到父节点的引用设为 Weak<T>
  3. 缓存系统:缓存项使用 Weak<T>,避免阻止数据释放
  4. 观察者模式:订阅者使用 Weak<T> 避免阻止发布者释放

练习

实现一个简单的社交网络系统,包含用户和朋友的关系。

要求:

用户结构:每个用户拥有一个名字和朋友列表。

添加朋友:支持在两个用户之间建立朋友关系。

展示朋友关系:能够展示每个用户的朋友列表。

循环引用:处理用户之间的双向引用,确保不产生循环引用。

/**
 * 实现一个简单的社交网络
 */
struct User {
    username: String,
    friends: RefCell<Vec<Weak<User>>>,
}

impl User {
    fn new(name: String) -> Rc<User> {
        Rc::new(User {
            username: name,
            friends: RefCell::new(vec![]),
        })
    }

    fn print_friends(&self) {
        for friend in self.friends.borrow().iter() {
            println!(
                "{}'s friends list: {}",
                self.username,
                friend.upgrade().unwrap().username
            );
        }
    }
}
fn add_friend(this: Rc<User>, other: Rc<User>) {
    this.friends.borrow_mut().push(Rc::downgrade(&other));
    other.friends.borrow_mut().push(Rc::downgrade(&this));
}
#[test]
fn test_social_network() {
    let alice = User::new("Alice".to_string());
    let bob = User::new("Bob".to_string());
    add_friend(alice.clone(), bob.clone());
    alice.print_friends();
    bob.print_friends();
}

最佳实践

  1. 优先选择

    • 优先用 Box<T> 保持所有权清晰
    • 确需共享时才使用 Rc<T>
  2. 组合使用

    • 配合 RefCell<T> 实现"内部可变性"
    • 配合 Weak<T> 避免循环引用
  3. 性能优化

    • 避免频繁 .clone() 传递所有权

    • Rc::make_mut获取唯一可变引用:

      let mut strong = Rc::new(10);
      let weak = Rc::downgrade(&strong);
      let unique = Rc::make_mut(&mut strong); // 若有其他引用会触发深拷贝
      *unique += 1;

通过合理使用 Rc<T>,可以在单线程场景下安全高效地实现数据共享,但需要特别注意其局限性和潜在风险。