Step 4 of 7
Rc<T>: shared ownership
Ownership says every value has exactly one owner. Most of the time that's what you want. But sometimes several parts of a program need to own the same value, and there's no obvious one that lives longest: a song that's on several playlists, a config shared by several services, a node in a graph with several incoming edges.
Rc<T> (reference counted) allows that. Rc::new(value) puts the value on the heap together with a counter. Rc::clone(&rc) makes another pointer to the same value and adds 1 to the strong count. Dropping an Rc subtracts 1. When the count reaches 0, the value is dropped.
use std::rc::Rc;
struct Song {
title: String,
}
fn main() {
let song = Rc::new(Song { title: String::from("Crab Rave") });
println!("count after new: {}", Rc::strong_count(&song));
let workout = vec![Rc::clone(&song)];
{
let road_trip = vec![Rc::clone(&song)];
println!("count with two playlists: {}", Rc::strong_count(&song));
println!("road trip plays {}", road_trip[0].title);
}
println!("count after the road trip: {}", Rc::strong_count(&song));
println!("workout plays {}", workout[0].title);
}
count after new: 1
count with two playlists: 3
road trip plays Crab Rave
count after the road trip: 2
workout plays Crab Rave
Rc::clone(&song)doesn't copy theSong. It copies a pointer and bumps the counter, which is very cheap.song.clone()does the same thing, but Rust code conventionally writesRc::clone(&song)so readers can see at a glance that it's a cheap pointer copy and not a deep copy.road_trip[0].titlereaches through theRcwithDeref, like a Box.- When the inner block ends,
road_tripis dropped, itsRcwith it, and the count goes from 3 to 2. The song itself stays alive until the lastRcis gone.
Rc is read-only and single-threaded
An Rc<T> only hands out shared references (&T). With several owners, letting any one of them mutate would break the "one writer" rule, so this doesn't compile:
use std::rc::Rc;
fn main() {
let scores = Rc::new(vec![1, 2]);
scores.push(3);
println!("{scores:?}");
}
error[E0596]: cannot borrow data in an `Rc` as mutable
To change shared data, combine Rc with RefCell (next two steps). And Rc updates its counter without any thread synchronization, so the compiler won't let you send it to another thread. Its thread-safe twin is Arc<T> (atomically reference counted), coming in the concurrency modules.
Common mistakes
- Expecting
Rc::cloneto copy the data. All clones point at the same value. - Reaching for Rc to quiet the borrow checker. If one owner clearly outlives the others, plain references (
&T) are simpler and free. Use Rc when ownership really is shared. - Trying to mutate through it:
error[E0596]as above.
Your task
Fill the blanks: make a and b share the same config with Rc::clone, and print the strong count. The program prints:
owners: 3
a sees retries = 5
owners after a is gone: 2
Challenge 2
Two lists can share the same tail when the tail is held in an Rc. Finish the program:
sharedis the list10 -> 20.ais1 -> sharedandbis2 -> shared(both point at the sameshared, usingRc::clone).- Write
fn sum(list: &List) -> i32.
The program must print exactly:
sum a = 31
sum b = 32
shared owners = 3
Challenge 3
A tournament player can be on several teams. Write:
fn share(player: Player, teams: &mut [Team]) -> Rc<Player>: wraps the player in oneRc, adds a clone of it to every team'smembers, and returns theRc(so the caller keeps one handle too).fn on_both(player: &Rc<Player>, a: &Team, b: &Team) -> bool: true if this exact player (the sameRc, not just the same name) is a member of both teams.Rc::ptr_eq(&x, &y)tells you whether twoRcs point at the same value.
Previous: Deref and Drop: what makes a pointer smart Next: RefCell and Cell: changing data behind &