Rc frees its data the moment its strong count hits zero -- but nothing forces that count to ever reach zero. Two Rcs that end up pointing back at each other, each counted as one of the other's owners, keep each other's count at one forever. Nobody panics, nothing errors: the memory is simply never freed, for as long as the program runs.
struct Node {
name: String,
next: RefCell<Option<Rc<Node>>>,
}
impl Drop for Node {
fn drop(&mut self) {
println!("dropping node {}", self.name); // should print for every node
}
}
let a = Rc::new(Node { name: "a".to_string(), next: RefCell::new(None) });
let b = Rc::new(Node { name: "b".to_string(), next: RefCell::new(None) });
*a.next.borrow_mut() = Some(Rc::clone(&b)); // a points to b
*b.next.borrow_mut() = Some(Rc::clone(&a)); // b points back to a -- a cycle
println!("a strong_count = {}", Rc::strong_count(&a)); // 2
drop(a);
drop(b);
// neither "dropping node" line ever prints -- the cycle keeps both alive foreverA smart pointer is a data structure that acts like a pointer but adds extra capabilities. Box<T> puts a value on the heap with ordinary single ownership, useful for recursive types or large data you don't want to copy. Rc<T> (reference counted) allows multiple owners of the same data by tracking a count of active references, freeing the data when the count hits zero — single-threaded only; the thread-safe equivalent is Arc<T>. RefCell<T> enforces the borrowing rules at runtime instead of compile time, allowing mutation through a reference that looks immutable, at the cost of a possible runtime panic instead of a compile error if the rules are violated.
Box<T>: heap allocation, single owner
Box is needed for recursive types, since Rust must know a type's size at compile time — a type that contains itself directly would be infinitely large, but a Box (a fixed-size pointer) breaks the cycle.
enum List {
Cons(i32, Box<List>), // Box breaks the infinite-size cycle
Nil,
}
use List::{Cons, Nil};
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));Rc<T>: multiple owners via reference counting
Rc::clone doesn't deep-copy the data — it increments a reference count and returns a new pointer to the same heap allocation. The data is freed only when the count reaches zero.
use std::rc::Rc;
let a = Rc::new(String::from("shared data"));
println!("{}", Rc::strong_count(&a)); // 1
let b = Rc::clone(&a); // increments the count, doesn't copy the string
println!("{}", Rc::strong_count(&a)); // 2
drop(b);
println!("{}", Rc::strong_count(&a)); // 1 againThat count is exactly what leaks in the bug above: each node's next holds a strong Rc to the other, so dropping your own two named bindings only brings each count from 2 down to 1 -- never to 0 -- and the Drop that would print "dropping node..." never runs. Weak<T> is the fix: a Weak reference doesn't count toward whether the data stays alive, so a back-reference built with Rc::downgrade instead of Rc::clone can't create a cycle that keeps itself alive:
struct Node {
name: String,
next: RefCell<Option<Weak<Node>>>, // a weak back-reference breaks the cycle
}
impl Drop for Node {
fn drop(&mut self) {
println!("dropping node {}", self.name);
}
}
let a = Rc::new(Node { name: "a".to_string(), next: RefCell::new(None) });
let b = Rc::new(Node { name: "b".to_string(), next: RefCell::new(None) });
*a.next.borrow_mut() = Some(Rc::downgrade(&b));
*b.next.borrow_mut() = Some(Rc::downgrade(&a));
println!("a strong_count = {}", Rc::strong_count(&a)); // 1
drop(a);
drop(b);
// dropping node a
// dropping node bRefCell<T>: interior mutability, checked at runtime
RefCell lets you mutate data even through what the type system sees as an immutable reference, by moving the borrow-checking rules to runtime — .borrow_mut() panics if another borrow is already active, exactly the rule the compiler would otherwise enforce statically.
use std::cell::RefCell;
let value = RefCell::new(5);
*value.borrow_mut() += 1; // mutate through a "shared" RefCell
println!("{}", value.borrow()); // 6
// let a = value.borrow_mut();
// let b = value.borrow_mut(); // RUNTIME PANIC: already mutably borrowed