首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Rust专项——智能指针与内在可变性详解(Box/Rc/Arc/Cell/RefCell/Mutex/RwLock)

Rust专项——智能指针与内在可变性详解(Box/Rc/Arc/Cell/RefCell/Mutex/RwLock)

作者头像
红目香薰
发布2025-12-16 16:31:13
发布2025-12-16 16:31:13
1300
举报
文章被收录于专栏:CSDNToQQCodeCSDNToQQCode

本章系统讲解 Rust 的智能指针内在可变性(Interior Mutability):掌握何时使用 BoxRc/Arc,何时选择 Cell/RefCell 或并发环境下的 Mutex/RwLock,写出既安全又高性能的工程代码。


1. 为什么需要智能指针?

  • 在所有权模型下,普通引用不持有资源的生命周期;智能指针封装了资源的所有权与生命周期,并提供访问/共享/同步的能力。
  • 常见能力:堆分配(Box)、引用计数共享(Rc/Arc)、运行时借用检查(RefCell)、并发互斥/读写锁(Mutex/RwLock)。

2. Box:在堆上存放一个值

  • 用于递归类型较大对象、或trait 对象的存放。
代码语言:javascript
复制
enum List { Cons(i32, Box<List>), Nil }

fn main() {
    let x = Box::new(42);
    println!("{}", x);
}

要点:Box<T> 实现了 Deref,可像 &T 一样使用;析构时自动释放堆内存。


3. Rc 与 Arc:引用计数共享所有权

  • Rc<T>:单线程下的共享,非并发安全
  • Arc<T>:原子引用计数,并发安全,在多线程中共享。
代码语言:javascript
复制
use std::rc::Rc;
let a = Rc::new(String::from("hello"));
let b = Rc::clone(&a);     // 增加计数
println!("{} {}", a, b);
  • 仅共享所有权,不允许可变借用(需配合 RefCell/Mutex)。

4. Cell 与 RefCell:内在可变性(单线程)

  • Cell<T>:按值替换(Copy 友好),提供 get/set/replace
  • RefCell<T>:运行时借用检查,提供 borrow()/borrow_mut()违反规则会 panic
代码语言:javascript
复制
use std::cell::RefCell;

struct Counter { inner: RefCell<i32> }
impl Counter { fn inc(&self) { *self.inner.borrow_mut() += 1; } }

fn main() {
    let c = Counter { inner: RefCell::new(0) };
    c.inc(); c.inc();
    println!("{}", c.inner.borrow());
}

适用:需要在 &self 方法中修改内部状态(如缓存、延迟初始化),但仅限单线程


5. Mutex 与 RwLock:并发环境下的内在可变性

  • Mutex<T>:互斥锁,独占写
  • RwLock<T>:读写锁,多读一写。
代码语言:javascript
复制
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..4 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || { *c.lock().unwrap() += 1; }));
    }
    for h in handles { h.join().unwrap(); }
    println!("{}", *counter.lock().unwrap());
}

注意:

  • lock() 返回守卫,出作用域自动解锁
  • 尽量缩小持锁范围,避免在持锁期间做 I/O 或长时间计算;
  • MutexRwLock 都是 Sync,配合 Arc 在多线程共享所有权。

6. 组合:Rc<RefCell> 与 Arc<Mutex>

  • 单线程共享 + 可变:Rc<RefCell<T>>
  • 多线程共享 + 可变:Arc<Mutex<T>> / Arc<RwLock<T>>
代码语言:javascript
复制
use std::cell::RefCell;
use std::rc::Rc;

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

fn main() {
    let n1 = Rc::new(RefCell::new(Node { val: 1, next: None }));
    let n2 = Rc::new(RefCell::new(Node { val: 2, next: Some(Rc::clone(&n1)) }));
    n1.borrow_mut().next = Some(Rc::clone(&n2));   // 可构建共享环
    println!("n1={:?}", n1);
}
在这里插入图片描述
在这里插入图片描述
  • 警惕循环引用导致内存泄漏(Rc 使用弱引用 Weak 断环)。

7. 弱引用 Weak:打破 Rc/Arc 的环

代码语言:javascript
复制
use std::rc::{Rc, Weak};
use std::cell::RefCell;

#[derive(Debug)]
struct Node { val: i32, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>> }
  • Weak<T> 不增加强计数,升级时用 upgrade() -> Option<Rc<T>>
  • 典型于树/图结构维护父子关系。

8. 选择指南(表)

需求

单线程

多线程

仅堆分配

Box<T>

Box<T>

共享只读

Rc<T>

Arc<T>

共享可变

Rc<RefCell<T>>

Arc<Mutex<T>> / Arc<RwLock<T>>

可选按值替换

Cell<T>

(不适用并发)

防环

Weak<T>

Weak<T>(配合 Arc)


9. 常见坑与修复

  • RefCell 里重复 borrow_mut() 导致运行时 panic → 缩小作用域或重构逻辑;
  • Mutex 守卫持有时间过长 → 分离计算与持锁阶段,尽量在锁外做重活;
  • Rc 用在多线程 → 崩溃,改用 Arc
  • 循环引用泄漏 → 父指向子用 Rc,子指回父用 Weak
  • 混用 Arc<RefCell<T>>(多线程 + 非线程安全)→ 用 Arc<Mutex<T>>Arc<RwLock<T>>

10. 实战:并发计数器与本地缓存

代码语言:javascript
复制
use std::sync::{Arc, Mutex, RwLock};
use std::collections::HashMap;
use std::thread;

fn counter_demo() {
    // 明确 Mutex 保护的类型是 usize
    let c = Arc::new(Mutex::new(0usize)); 
    let mut hs = vec![];
    for _ in 0..8 {
        let cc = Arc::clone(&c);
        hs.push(thread::spawn(move || {
            // 锁定并修改值
            *cc.lock().unwrap() += 1; 
        }));
    }
    // 等待所有线程完成
    for h in hs { 
        h.join().unwrap(); 
    }
    // 输出最终计数
    println!("count={}", *c.lock().unwrap()); 
}

fn cache_demo() {
    // 显式指定 HashMap 的键值类型为 String, String
    let cache: Arc<RwLock<HashMap<String, String>>> = Arc::new(RwLock::new(HashMap::new())); 
    {
        // 获取写锁并插入数据
        let mut w = cache.write().unwrap(); 
        w.insert("k".into(), "v".into()); 
    }
    // 获取读锁并读取数据
    let r = cache.read().unwrap(); 
    println!("{:?}", r.get("k")); 
}

fn main() {
    counter_demo();
    cache_demo();
}
在这里插入图片描述
在这里插入图片描述

11. 练习

  1. Rc<RefCell<T>> 实现一个可共享修改的双向链表节点,思考如何用 Weak 打破环;
  2. 将某个服务的共享状态改造为 Arc<RwLock<...>>,对比读多写少的性能;
  3. Counter 增加 fetch_addreset 接口,确保不会死锁(合理划分锁粒度)。

小结

  • Box 管堆分配;Rc/Arc 管共享;Cell/RefCell 管单线程可变;Mutex/RwLock 管并发可变;
  • Weak 断环、PhantomData 标注借用、守卫对象 RAII 释放;
  • 合理选择恰当工具,是写出稳定性能与工程可维护代码的关键。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 为什么需要智能指针?
  • 2. Box:在堆上存放一个值
  • 3. Rc 与 Arc:引用计数共享所有权
  • 4. Cell 与 RefCell:内在可变性(单线程)
  • 5. Mutex 与 RwLock:并发环境下的内在可变性
  • 6. 组合:Rc<RefCell> 与 Arc<Mutex>
  • 7. 弱引用 Weak:打破 Rc/Arc 的环
  • 8. 选择指南(表)
  • 9. 常见坑与修复
  • 10. 实战:并发计数器与本地缓存
  • 11. 练习
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档