Guide contents

Lesson 57 of 94

Rc & Weak: Shared Ownership

Ownership says every value has exactly one owner. Sometimes that's not the right model: a configuration read by many services, a graph where several nodes point at the same node, a cache entry handed to many callers.

Rc<T> ("reference counted") supports this explicitly, for one value at a time. Cloning an Rc doesn't copy the value: it creates another owner and increments a counter. When the last Rc is dropped, the count reaches zero and the value is freed, immediately and deterministically.

Concept Rust
Another owner let c = Rc::clone(&shared);
When the value is freed The last Rc drop frees it immediately
Cycles Cycles leak: break them with Weak
Non-owning reference Weak<T>
Threads Rc is single-threaded; use Arc across threads

1. Several owners of one value

Rust
use std::rc::Rc;

fn main() {
    let config = Rc::new(String::from("region=eu-west-1"));
    let for_api = Rc::clone(&config);
    let for_worker = Rc::clone(&config);
    println!("{config} / {for_api} / {for_worker}");
    println!("owners: {}", Rc::strong_count(&config));
}
Output
region=eu-west-1 / region=eu-west-1 / region=eu-west-1
owners: 3

Rc::clone(&x) is the idiomatic spelling. x.clone() does the same thing, but writing Rc::clone makes it obvious to readers that this is a cheap counter increment, not a deep copy.

2. The value is freed when the last owner goes away

Rust
use std::rc::Rc;

struct Connection(&'static str);

impl Drop for Connection {
    fn drop(&mut self) {
        println!("closing {}", self.0);
    }
}

fn main() {
    let a = Rc::new(Connection("db"));
    println!("count = {}", Rc::strong_count(&a));
    {
        let b = Rc::clone(&a);
        println!("count = {} (b alive)", Rc::strong_count(&b));
    }
    println!("count = {} (b dropped)", Rc::strong_count(&a));
    drop(a);
    println!("after last owner dropped");
}
Output
count = 1
count = 2 (b alive)
count = 1 (b dropped)
closing db
after last owner dropped

3. Cloning an Rc doesn't clone the value

Rust
use std::rc::Rc;

fn main() {
    let big = Rc::new(vec![0_u8; 1_000_000]);
    let copies: Vec<Rc<Vec<u8>>> = (0..1_000).map(|_| Rc::clone(&big)).collect();
    println!("{} handles, one 1 MB buffer, count {}", copies.len(), Rc::strong_count(&big));
    println!("same allocation: {}", Rc::ptr_eq(&big, &copies[999]));
}
Output
1000 handles, one 1 MB buffer, count 1001
same allocation: true

Rc::ptr_eq checks whether two Rcs point to the same value.

4. Why not just use references?

A reference &T must not outlive its owner, and the compiler must be able to see that. When there's no single owner that clearly outlives everything else, use Rc.

Rust
use std::rc::Rc;

struct Service {
    name: &'static str,
    settings: Rc<Vec<(&'static str, &'static str)>>,
}

fn build_services() -> Vec<Service> {
    let settings = Rc::new(vec![("timeout", "30s"), ("region", "eu")]);
    vec![
        Service { name: "api", settings: Rc::clone(&settings) },
        Service { name: "worker", settings: Rc::clone(&settings) },
    ]
    // `settings` (the local handle) is dropped here, but the data lives on in the services
}

fn main() {
    let services = build_services();
    for s in &services {
        println!("{} sees {:?}", s.name, s.settings);
    }
}
Output
api sees [("timeout", "30s"), ("region", "eu")]
worker sees [("timeout", "30s"), ("region", "eu")]

With plain references, settings would have to be owned by something outside build_services that outlives both services.

5. Rc<T> only gives shared (read-only) access

Because several owners may hold the same value, Rc never hands out &mut T. Changing a value through an Rc needs interior mutability (RefCell, the next lesson), or copy-on-write (example 7).

Rust✗ does not compile · E0596
use std::rc::Rc;

fn main() {
    let shared = Rc::new(vec![1, 2, 3]);
    let other = Rc::clone(&shared);
    shared.push(4);
    println!("{:?}", other);
}
Output
error[E0596]: cannot borrow data in an `Rc` as mutable
 --> src/main.rs:6:5
  |
6 |     shared.push(4);
  |     ^^^^^^ cannot borrow as mutable
  |
  = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<Vec<i32>>`

6. Rc<str> and Rc<[T]> for shared immutable data

A string or slice that many places read, and nobody changes, can be shared as Rc<str> or Rc<[T]>. Cloning is a counter increment instead of a string copy.

Rust
use std::rc::Rc;

fn main() {
    let tenant: Rc<str> = Rc::from("acme-corp");
    let events: Vec<(Rc<str>, &str)> = ["login", "upload", "logout"].iter().map(|e| (Rc::clone(&tenant), *e)).collect();
    for (t, e) in &events {
        println!("[{t}] {e}");
    }
    println!("one string, {} owners", Rc::strong_count(&tenant));

    let primes: Rc<[u32]> = Rc::from(vec![2, 3, 5, 7]);
    let alias = Rc::clone(&primes);
    println!("{:?} {}", alias, alias.len());
}
Output
[acme-corp] login
[acme-corp] upload
[acme-corp] logout
one string, 4 owners
[2, 3, 5, 7] 4

7. Rc::make_mut: copy-on-write

make_mut gives a &mut T. If this Rc is the only owner, it changes the value in place. If there are other owners, it first clones the value so they keep seeing the old one.

Rust
use std::rc::Rc;

fn main() {
    let original = Rc::new(vec!["a", "b"]);
    let mut edited = Rc::clone(&original);

    Rc::make_mut(&mut edited).push("c"); // shared: clones first
    println!("original {:?}, edited {:?}", original, edited);
    println!("shared now? {}", Rc::ptr_eq(&original, &edited));

    Rc::make_mut(&mut edited).push("d"); // edited is the only owner of its copy: no clone
    println!("edited {:?}, count {}", edited, Rc::strong_count(&edited));
}
Output
original ["a", "b"], edited ["a", "b", "c"]
shared now? false
edited ["a", "b", "c", "d"], count 1

Copy-on-write makes snapshots cheap: share freely, and pay for a copy only when someone actually changes the data.

8. Getting the value back with try_unwrap

Rc::try_unwrap returns the inner value if this is the last owner, or gives the Rc back otherwise.

Rust
use std::rc::Rc;

fn main() {
    let report = Rc::new(String::from("final report"));
    let viewer = Rc::clone(&report);

    let report = match Rc::try_unwrap(report) {
        Ok(s) => panic!("unexpected sole owner: {s}"),
        Err(still_shared) => still_shared,
    };
    drop(viewer);
    let owned: String = Rc::try_unwrap(report).expect("viewer dropped, so we are the only owner");
    println!("got owned String: {owned}");
}
Output
got owned String: final report

Rc::unwrap_or_clone(rc) is the shortcut: it returns the value, cloning it only if needed.

9. Shared nodes in a graph

Several nodes can point to the same child. With Box, a child could only have one parent.

Rust
use std::rc::Rc;

#[derive(Debug)]
struct Module {
    name: &'static str,
    deps: Vec<Rc<Module>>,
}

fn main() {
    let logging = Rc::new(Module { name: "logging", deps: vec![] });
    let http = Rc::new(Module { name: "http", deps: vec![Rc::clone(&logging)] });
    let db = Rc::new(Module { name: "db", deps: vec![Rc::clone(&logging)] });
    let app = Module { name: "app", deps: vec![Rc::clone(&http), Rc::clone(&db)] };

    for d in &app.deps {
        let names: Vec<&str> = d.deps.iter().map(|m| m.name).collect();
        println!("{} -> {} -> {:?}", app.name, d.name, names);
    }
    println!("logging is shared by {} owners", Rc::strong_count(&logging));
}
Output
app -> http -> ["logging"]
app -> db -> ["logging"]
logging is shared by 3 owners

10. Rc is not thread-safe

Rc uses a plain counter, which would be corrupted by two threads updating it at once. The compiler refuses to send an Rc to another thread. Use Arc ("atomically reference counted") there instead; it has the same API. See Shared State.

Rust✗ does not compile · E0277
use std::rc::Rc;
use std::thread;

fn main() {
    let data = Rc::new(vec![1, 2, 3]);
    let handle = thread::spawn(move || println!("{:?}", data));
    handle.join().unwrap();
}
Output
error[E0277]: `Rc<Vec<i32>>` cannot be sent between threads safely
 --> src/main.rs:6:32
  |
6 |     let handle = thread::spawn(move || println!("{:?}", data));
  |                  ------------- -------^^^^^^^^^^^^^^^^^^^^^^^
  |                  |             |
  |                  |             `Rc<Vec<i32>>` cannot be sent between threads safely
  |                  |             within this `{closure@src/main.rs:6:32: 6:39}`
  |                  required by a bound introduced by this call
  |
  = help: within `{closure@src/main.rs:6:32: 6:39}`, the trait `Send` is not implemented for `Rc<Vec<i32>>`
note: required because it's used within this closure
 --> src/main.rs:6:32
  |
6 |     let handle = thread::spawn(move || println!("{:?}", data));
  |                                ^^^^^^^
note: required by a bound in `spawn`
 --> /rustc/<version>/library/std/src/thread/functions.rs:125:0
Rust
use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    let worker_copy = Arc::clone(&data);
    let handle = thread::spawn(move || worker_copy.iter().sum::<i32>());
    println!("sum from thread: {}, owners after join: {}", handle.join().unwrap(), Arc::strong_count(&data));
}
Output
sum from thread: 6, owners after join: 1

Rc is slightly faster because it avoids atomic operations, which is why both exist.

11. Reference cycles leak memory

If a holds an Rc to b and b holds an Rc to a, neither count ever reaches zero, so neither is freed. (Building a cycle requires mutation through an Rc, so this example uses RefCell, from the next lesson.)

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

struct Node {
    name: &'static str,
    other: RefCell<Option<Rc<Node>>>,
}

impl Drop for Node {
    fn drop(&mut self) {
        println!("dropping {}", self.name);
    }
}

fn main() {
    {
        let a = Rc::new(Node { name: "a", other: RefCell::new(None) });
        let b = Rc::new(Node { name: "b", other: RefCell::new(Some(Rc::clone(&a))) });
        *a.other.borrow_mut() = Some(Rc::clone(&b)); // a -> b -> a: a cycle
        println!("a count {}, b count {}", Rc::strong_count(&a), Rc::strong_count(&b));
    }
    println!("end of scope: nothing was dropped, the cycle leaked");
}
Output
a count 2, b count 2
end of scope: nothing was dropped, the cycle leaked

12. Weak<T>: a reference that doesn't keep the value alive

A Weak points to an Rc's value without counting as an owner. To use it, call upgrade(), which returns Some(Rc<T>) if the value still exists or None if it was dropped.

Rust
use std::rc::{Rc, Weak};

fn main() {
    let session = Rc::new(String::from("session-42"));
    let observer: Weak<String> = Rc::downgrade(&session);
    println!("strong {}, weak {}", Rc::strong_count(&session), Rc::weak_count(&session));
    println!("upgrade while alive: {:?}", observer.upgrade());
    drop(session);
    println!("upgrade after drop: {:?}", observer.upgrade());
}
Output
strong 1, weak 1
upgrade while alive: Some("session-42")
upgrade after drop: None

13. Parent pointers with Weak

The classic use of Weak: children own nothing of their parent, so parent ↔ child links don't form a cycle. Parents own children with Rc; children refer to parents with Weak.

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

struct Dept {
    name: &'static str,
    parent: RefCell<Weak<Dept>>,
    children: RefCell<Vec<Rc<Dept>>>,
}

impl Dept {
    fn new(name: &'static str) -> Rc<Dept> {
        Rc::new(Dept { name, parent: RefCell::new(Weak::new()), children: RefCell::new(Vec::new()) })
    }

    fn add_child(parent: &Rc<Dept>, child: Rc<Dept>) {
        *child.parent.borrow_mut() = Rc::downgrade(parent);
        parent.children.borrow_mut().push(child);
    }

    fn path(&self) -> String {
        match self.parent.borrow().upgrade() {
            Some(p) => format!("{} / {}", p.path(), self.name),
            None => self.name.to_string(),
        }
    }
}

fn main() {
    let company = Dept::new("Acme");
    let eng = Dept::new("Engineering");
    let platform = Dept::new("Platform");
    Dept::add_child(&company, Rc::clone(&eng));
    Dept::add_child(&eng, Rc::clone(&platform));
    println!("{}", platform.path());
    println!("Acme has {} child(ren); Engineering strong count {}", company.children.borrow().len(), Rc::strong_count(&eng));
}
Output
Acme / Engineering / Platform
Acme has 1 child(ren); Engineering strong count 2

14. Dropping the root frees the whole tree

Because children only hold Weak references upward, dropping the owners frees everything, with no leak.

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

struct Node {
    name: &'static str,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

impl Drop for Node {
    fn drop(&mut self) {
        println!("freed {}", self.name);
    }
}

fn main() {
    let leaf_handle;
    {
        let root = Rc::new(Node { name: "root", parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]) });
        let leaf = Rc::new(Node { name: "leaf", parent: RefCell::new(Rc::downgrade(&root)), children: RefCell::new(vec![]) });
        root.children.borrow_mut().push(Rc::clone(&leaf));
        leaf_handle = Rc::downgrade(&leaf);
        println!("leaf's parent: {:?}", leaf.parent.borrow().upgrade().map(|p| p.name));
    }
    println!("leaf still alive? {}", leaf_handle.upgrade().is_some());
}
Output
leaf's parent: Some("root")
freed root
freed leaf
leaf still alive? false

15. A weak cache: don't keep things alive just because they're cached

A cache holding Weak references returns an object while someone is still using it, but doesn't prevent it from being freed.

Rust
use std::collections::HashMap;
use std::rc::{Rc, Weak};

struct TextureCache {
    entries: HashMap<&'static str, Weak<Vec<u8>>>,
    loads: u32,
}

impl TextureCache {
    fn get(&mut self, name: &'static str) -> Rc<Vec<u8>> {
        if let Some(tex) = self.entries.get(name).and_then(Weak::upgrade) {
            return tex;
        }
        self.loads += 1;
        let tex = Rc::new(vec![self.loads as u8; 4]); // pretend this is loaded from disk
        self.entries.insert(name, Rc::downgrade(&tex));
        tex
    }
}

fn main() {
    let mut cache = TextureCache { entries: HashMap::new(), loads: 0 };
    let a = cache.get("grass.png");
    let b = cache.get("grass.png"); // reused: `a` keeps it alive
    println!("same texture: {}, loads {}", Rc::ptr_eq(&a, &b), cache.loads);
    drop(a);
    drop(b);
    let c = cache.get("grass.png"); // everyone dropped it, so it's reloaded
    println!("reloaded: {:?}, loads {}", c, cache.loads);
}
Output
same texture: true, loads 1
reloaded: [2, 2, 2, 2], loads 2

16. Counting owners for diagnostics

strong_count and weak_count are handy for tests and debugging, like inspecting reference counts in a heap dump.

Rust
use std::rc::Rc;

fn main() {
    let theme = Rc::new("dark");
    let widgets: Vec<Rc<&str>> = (0..5).map(|_| Rc::clone(&theme)).collect();
    let weak_listeners: Vec<_> = (0..2).map(|_| Rc::downgrade(&theme)).collect();
    println!("strong {}, weak {}", Rc::strong_count(&theme), Rc::weak_count(&theme));
    drop(widgets);
    println!("strong {}, weak {}", Rc::strong_count(&theme), Rc::weak_count(&theme));
    println!("listeners can still see it: {}", weak_listeners.iter().all(|w| w.upgrade().is_some()));
}
Output
strong 6, weak 2
strong 1, weak 2
listeners can still see it: true

17. Rc in function signatures

Take &T when a function only reads the value; take Rc<T> only when it needs to keep a share for later.

Rust
use std::rc::Rc;

struct Logger {
    prefix: Rc<str>,
}

fn print_prefix(prefix: &str) {
    println!("prefix is {prefix}"); // only reads: borrow
}

fn make_logger(prefix: &Rc<str>) -> Logger {
    Logger { prefix: Rc::clone(prefix) } // keeps a share: clone the Rc
}

fn main() {
    let app_name: Rc<str> = Rc::from("checkout");
    print_prefix(&app_name);
    let l1 = make_logger(&app_name);
    let l2 = make_logger(&app_name);
    println!("{} {} owners={}", l1.prefix, l2.prefix, Rc::strong_count(&app_name));
}
Output
prefix is checkout
checkout checkout owners=3

18. Realistic example: an immutable, shareable document history

Each version shares unchanged paragraphs with the previous version, so keeping 100 versions doesn't mean 100 full copies.

Rust
use std::rc::Rc;

#[derive(Clone)]
struct Version {
    paragraphs: Vec<Rc<String>>,
}

impl Version {
    fn edit(&self, index: usize, text: &str) -> Version {
        let mut paragraphs = self.paragraphs.clone(); // clones the Rc handles, not the text
        paragraphs[index] = Rc::new(text.to_string());
        Version { paragraphs }
    }
}

fn main() {
    let v1 = Version { paragraphs: vec![Rc::new(String::from("Intro")), Rc::new(String::from("Body")), Rc::new(String::from("Conclusion"))] };
    let v2 = v1.edit(1, "Better body");
    let v3 = v2.edit(2, "Better conclusion");

    for (i, v) in [&v1, &v2, &v3].iter().enumerate() {
        let texts: Vec<&str> = v.paragraphs.iter().map(|p| p.as_str()).collect();
        println!("v{}: {:?}", i + 1, texts);
    }
    println!("'Intro' shared by all 3 versions: {}", Rc::strong_count(&v1.paragraphs[0]) == 3);
}
Output
v1: ["Intro", "Body", "Conclusion"]
v2: ["Intro", "Better body", "Conclusion"]
v3: ["Intro", "Better body", "Better conclusion"]
'Intro' shared by all 3 versions: true

This structural sharing is how persistent (immutable) data structures work in functional languages and in editors' undo systems.

19. Realistic example: an observer that doesn't keep subjects alive

Listeners register with a Weak reference, so a dropped listener is skipped (and cleaned up) instead of being kept alive forever.

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

struct Listener {
    name: &'static str,
    received: RefCell<Vec<String>>,
}

struct EventBus {
    listeners: Vec<Weak<Listener>>,
}

impl EventBus {
    fn publish(&mut self, event: &str) {
        self.listeners.retain(|w| w.upgrade().is_some()); // forget dropped listeners
        for w in &self.listeners {
            if let Some(l) = w.upgrade() {
                l.received.borrow_mut().push(event.to_string());
            }
        }
    }
}

fn main() {
    let mut bus = EventBus { listeners: Vec::new() };
    let dashboard = Rc::new(Listener { name: "dashboard", received: RefCell::new(vec![]) });
    let popup = Rc::new(Listener { name: "popup", received: RefCell::new(vec![]) });
    bus.listeners.push(Rc::downgrade(&dashboard));
    bus.listeners.push(Rc::downgrade(&popup));

    bus.publish("price changed");
    drop(popup); // the popup window was closed
    bus.publish("order filled");

    println!("{} got {:?}", dashboard.name, dashboard.received.borrow());
    println!("bus now tracks {} listener(s)", bus.listeners.len());
}
Output
dashboard got ["price changed", "order filled"]
bus now tracks 1 listener(s)

20. Realistic example: a shared read-only catalog

Many carts reference the same product records. Rc<Product> avoids copying product data into every cart line.

Rust
use std::collections::HashMap;
use std::rc::Rc;

#[derive(Debug)]
struct Product {
    sku: &'static str,
    name: &'static str,
    price_cents: u64,
}

struct Cart {
    lines: Vec<(Rc<Product>, u32)>,
}

impl Cart {
    fn total(&self) -> u64 {
        self.lines.iter().map(|(p, q)| p.price_cents * *q as u64).sum()
    }
}

fn main() {
    let catalog: HashMap<&str, Rc<Product>> = [
        Product { sku: "KB-1", name: "Keyboard", price_cents: 12_900 },
        Product { sku: "MS-2", name: "Mouse", price_cents: 3_900 },
    ]
    .into_iter()
    .map(|p| (p.sku, Rc::new(p)))
    .collect();

    let cart_a = Cart { lines: vec![(Rc::clone(&catalog["KB-1"]), 1), (Rc::clone(&catalog["MS-2"]), 2)] };
    let cart_b = Cart { lines: vec![(Rc::clone(&catalog["MS-2"]), 1)] };
    println!("cart A {} cents, cart B {} cents", cart_a.total(), cart_b.total());
    println!("{} is referenced by {} owners", catalog["MS-2"].name, Rc::strong_count(&catalog["MS-2"]));
}
Output
cart A 20700 cents, cart B 3900 cents
Mouse is referenced by 3 owners

21. Realistic example: a doubly linked list with Weak back pointers

Forward links own the next node (Rc); backward links don't (Weak), so there's no cycle.

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

struct Node {
    value: i32,
    next: RefCell<Option<Rc<Node>>>,
    prev: RefCell<Weak<Node>>,
}

fn build(values: &[i32]) -> Option<Rc<Node>> {
    let mut head: Option<Rc<Node>> = None;
    let mut tail: Option<Rc<Node>> = None;
    for &v in values {
        let node = Rc::new(Node { value: v, next: RefCell::new(None), prev: RefCell::new(Weak::new()) });
        match &tail {
            Some(t) => {
                *node.prev.borrow_mut() = Rc::downgrade(t);
                *t.next.borrow_mut() = Some(Rc::clone(&node));
            }
            None => head = Some(Rc::clone(&node)),
        }
        tail = Some(node);
    }
    head
}

fn main() {
    let head = build(&[1, 2, 3, 4]).unwrap();
    let mut forward = Vec::new();
    let mut current = Some(Rc::clone(&head));
    let mut last = None;
    while let Some(node) = current {
        forward.push(node.value);
        current = node.next.borrow().clone();
        last = Some(node);
    }
    let mut backward = Vec::new();
    let mut current = last;
    while let Some(node) = current {
        backward.push(node.value);
        current = node.prev.borrow().upgrade();
    }
    println!("forward {:?}, backward {:?}", forward, backward);
}
Output
forward [1, 2, 3, 4], backward [4, 3, 2, 1]

In real code, use VecDeque or std::collections::LinkedList; this shows how Rc/Weak structures are built.

22. Realistic example: an interned string pool

Repeated strings (tag names, event types) are stored once and shared as Rc<str>, cutting memory for large datasets.

Rust
use std::collections::HashSet;
use std::rc::Rc;

struct Interner {
    pool: HashSet<Rc<str>>,
}

impl Interner {
    fn intern(&mut self, s: &str) -> Rc<str> {
        if let Some(existing) = self.pool.get(s) {
            return Rc::clone(existing);
        }
        let new: Rc<str> = Rc::from(s);
        self.pool.insert(Rc::clone(&new));
        new
    }
}

fn main() {
    let mut interner = Interner { pool: HashSet::new() };
    let raw_events = ["click", "view", "click", "click", "purchase", "view"];
    let events: Vec<Rc<str>> = raw_events.iter().map(|e| interner.intern(e)).collect();
    println!("{} events, {} distinct strings stored", events.len(), interner.pool.len());
    println!("'click' shared by {} handles", Rc::strong_count(&events[0]));
}
Output
6 events, 3 distinct strings stored
'click' shared by 4 handles

HashSet<Rc<str>> can be looked up with a &str because Rc<str> borrows as str, the same mechanism that lets HashMap<String, V> be queried with &str.

23. Realistic example: undo snapshots with make_mut

The editor keeps snapshots as Rcs. Taking a snapshot is a counter increment; the next edit triggers one real copy, and only if a snapshot is still sharing the data.

Rust
use std::rc::Rc;

struct Editor {
    doc: Rc<Vec<String>>,
    history: Vec<Rc<Vec<String>>>,
}

impl Editor {
    fn snapshot(&mut self) {
        self.history.push(Rc::clone(&self.doc));
    }

    fn append(&mut self, line: &str) {
        let copied = Rc::strong_count(&self.doc) > 1;
        Rc::make_mut(&mut self.doc).push(line.to_string());
        println!("append {line:?} (copied: {copied})");
    }

    fn undo(&mut self) {
        if let Some(prev) = self.history.pop() {
            self.doc = prev;
        }
    }
}

fn main() {
    let mut e = Editor { doc: Rc::new(Vec::new()), history: Vec::new() };
    e.append("line 1");
    e.snapshot();
    e.append("line 2");
    e.append("line 3");
    println!("{:?}", e.doc);
    e.undo();
    println!("after undo {:?}", e.doc);
}
Output
append "line 1" (copied: false)
append "line 2" (copied: true)
append "line 3" (copied: false)
["line 1", "line 2", "line 3"]
after undo ["line 1"]

24. Rc vs Box vs references

You need Use
One owner, value on the heap Box<T>
Temporary access, clear owner that outlives the user &T / &mut T
Several owners, single thread, read-only Rc<T>
Several owners, single thread, mutable Rc<RefCell<T>> (next lesson)
Several owners across threads Arc<T> / Arc<Mutex<T>> (Shared State)
A non-owning back pointer or cache entry Weak<T>
Rust
use std::rc::Rc;

fn main() {
    let boxed = Box::new(1);
    let shared = Rc::new(1);
    let shared2 = Rc::clone(&shared);
    let borrowed = &*boxed;
    println!("{boxed} {shared} {shared2} {borrowed} (Rc owners: {})", Rc::strong_count(&shared));
}
Output
1 1 1 1 (Rc owners: 2)

Summary

  • Rc<T> lets several owners share one value. Rc::clone increments a count; the value is freed when the last owner is dropped.
  • Rc only gives shared access. Use Rc::make_mut for copy-on-write, or RefCell for shared mutation.
  • Rc::ptr_eq compares identity; strong_count and weak_count report owners.
  • Rc cycles leak memory. Use Weak for parent pointers, back links, observers, and caches, and upgrade() to use a Weak.
  • Rc is single-threaded. Arc is its thread-safe equivalent.

Next: Cell & RefCell.