智能指针与内部可变性 intermediate 25 分钟 更新 2026-06-15

Rc 共享所有权

用 Rc<T> 让多个所有者共享同一份只读数据。

Rc 共享所有权

你会学到什么

  • Rc<T> 通过引用计数允许多个所有者共享同一份数据。
  • Rc::clone 只增加计数,不复制底层数据,代价很小。
  • Rc 只用于单线程;跨线程要用 Arc(并发阶段介绍)。

最小示例

use std::rc::Rc;

let shared = Rc::new(String::from("data"));
let a = Rc::clone(&shared);
println!("count = {}", Rc::strong_count(&shared)); // 2

运行代码

cd examples
cargo run -p rt_23_rc
cargo test -p rt_23_rc

代码讲解

Rc::clone 是浅拷贝——只是把引用计数加一,所有克隆指向同一块堆数据:

let a = Rc::clone(&shared);
let b = Rc::clone(&shared);
// strong_count == 3

当最后一个 Rc 被 drop、计数归零时,数据才被释放。Rc::ptr_eq 可以判断两个 Rc 是否指向同一块内存。

习惯上写 Rc::clone(&x) 而不是 x.clone(),以强调这是“增加引用计数”而非深拷贝。

常见错误

想通过 Rc 修改共享数据:

let shared = Rc::new(vec![1, 2, 3]);
shared.push(4); // ❌ Rc<T> 默认不可变

Rc<T> 只给共享只读访问。要共享 + 可变,需要 Rc<RefCell<T>>(下一章)。

练习

  • 构造一个树,让两个父节点共享同一个子节点。
  • 打印每次 clonedrop 后的 strong_count,观察变化。

小结

Rc<T> 用引用计数实现共享所有权,适合“一份数据、多处只读引用”的场景,仅限单线程。

下一步

Rc 共享只读,要在共享时还能修改,就需要内部可变性。下一章学习 RefCell

完整示例代码

下面是 examples/23_rc/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。

examples/23_rc/src/main.rs
//! Rc<T>:单线程下的共享所有权(引用计数)。

use std::rc::Rc;

/// 一个被多处共享的配置。
#[derive(Debug, PartialEq)]
struct Config {
    name: String,
}

fn main() {
    let shared = Rc::new(Config {
        name: "prod".to_string(),
    });
    println!("count after create = {}", Rc::strong_count(&shared));

    // clone 只增加引用计数,不复制底层数据。
    let a = Rc::clone(&shared);
    let b = Rc::clone(&shared);
    println!("count after 2 clones = {}", Rc::strong_count(&shared));
    println!("a.name = {}, b.name = {}", a.name, b.name);

    drop(a);
    drop(b);
    println!("count after drops = {}", Rc::strong_count(&shared));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn clone_increases_count() {
        let shared = Rc::new(Config {
            name: "x".to_string(),
        });
        assert_eq!(Rc::strong_count(&shared), 1);
        let clone = Rc::clone(&shared);
        assert_eq!(Rc::strong_count(&shared), 2);
        drop(clone);
        assert_eq!(Rc::strong_count(&shared), 1);
    }

    #[test]
    fn clones_share_data() {
        let shared = Rc::new(Config {
            name: "same".to_string(),
        });
        let clone = Rc::clone(&shared);
        // 指向同一块数据。
        assert!(Rc::ptr_eq(&shared, &clone));
    }
}
examples/23_rc/Cargo.toml
[package]
name = "rt_23_rc"
version.workspace = true
edition.workspace = true
publish.workspace = true