Skip to content
Learn Rust
Theme

Checkpoint: Smart pointers

Seven short programs on the Smart pointers part: Rc and Weak, Drop, interior mutability, Rc across threads, and the errors the compiler gives, borrow checker ones among them. Question 4 uses thread::spawn, which the Smart pointers lesson previews at the end; nothing else here comes from a later part.

How to use it: read the program, write down your answer (the output, or the error code), then open the answer. Every answer is a real run with Rust 1.99.1, edition 2024. The programs that do not compile are kept apart in the project, so the others build.

Rustsrc/bin/q1.rs
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("shared"));
let b = Rc::clone(&a);
let w = Rc::downgrade(&a);
println!("{} {}", Rc::strong_count(&a), Rc::weak_count(&a));
drop(a);
println!("{:?}", w.upgrade());
drop(b);
println!("{:?}", w.upgrade());
}

What does it print?

Answer
terminal
$ cargo run --bin q1
2 1
Some("shared")
None

Rc::clone made two owners. Rc::downgrade adds a weak count, which does not keep the value alive. After drop(a) one owner (b) was left, so upgrade found the value; after drop(b) none was, so it found nothing. Taught in Cycles and Weak<T>.

Rustsrc/bin/q2.rs
use std::rc::Rc;
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!("drop {}", self.0);
}
}
fn main() {
let first = Rc::new(Noisy("first"));
let second = Rc::new(Noisy("second"));
let extra = Rc::clone(&first);
drop(first);
println!("dropped first");
let boxed = Box::new(Noisy("boxed"));
println!("end of main: {} {} {}", second.0, extra.0, boxed.0);
}

Which lines appear, and in which order?

Answer
terminal
$ cargo run --bin q2
dropped first
end of main: second first boxed
drop boxed
drop first
drop second

drop(first) gave up one of two owners, so nothing printed there. The value is dropped when the last owner goes, and extra was that owner. At the closing brace, locals are dropped in the reverse of the order they were declared: boxed, then extra, then second. Taught in Rc<T> and Drop.

Rustsrc/bin/q3.rs
use std::rc::Rc;
fn main() {
let mut mine = Rc::new(vec![1, 2]);
let theirs = Rc::clone(&mine);
Rc::make_mut(&mut mine).push(3);
println!("{:?} {:?}", mine, theirs);
println!("{} {}", Rc::strong_count(&mine), Rc::strong_count(&theirs));
Rc::make_mut(&mut mine).push(4);
println!("{:?}", mine);
}

What does it print?

Answer
terminal
$ cargo run --bin q3
[1, 2, 3] [1, 2]
1 1
[1, 2, 3, 4]

Rc::make_mut saw a second owner, so it copied the Vec for mine before the push. theirs kept the old one, and each count dropped to 1. Now mine is alone, so the second push changes it in place (the lesson’s make-mut program checks that the allocation did not move). Taught in Rc<T>.

Rustq4.rs
use std::rc::Rc;
use std::thread;
fn main() {
let numbers = Rc::new(vec![1, 2, 3]);
let worker = {
let numbers = Rc::clone(&numbers);
thread::spawn(move || println!("{}", numbers.len()))
};
worker.join().unwrap();
println!("{}", numbers.len());
}

Does it compile? If not, which error code, and which trait is missing?

Answer
$ cargo run --bin q4
error[E0277]: `Rc<Vec<i32>>` cannot be sent between threads safely
 --> src/bin/q4.rs:8:23
  |
8 |         thread::spawn(move || println!("{}", numbers.len()))
  |         ------------- -------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |         |             |
  |         |             `Rc<Vec<i32>>` cannot be sent between threads safely
  |         |             within this `{closure@src/bin/q4.rs:8:23: 8:30}`
  |         required by a bound introduced by this call
  |
  = help: within `{closure@src/bin/q4.rs:8:23: 8:30}`, the trait `Send` is not implemented for `Rc<Vec<i32>>`
note: required because it's used within this closure
 --> src/bin/q4.rs:8:23
  |
8 |         thread::spawn(move || println!("{}", numbers.len()))
  |                       ^^^^^^^
  ... (notes pointing into the standard library trimmed)

E0277: Rc<Vec<i32>> is not Send. An Rc changes its count without protection, so thread::spawn refuses a closure that owns one. Arc is the same API with an atomic count. Taught in Across threads: Arc<T> and Send and Sync.

Rustq5.rs
fn main() {
let mut count = 0;
let mut up = || count += 1;
let mut down = || count -= 1;
up();
down();
println!("{count}");
}

Both closures only add or subtract one. Does it compile? If not, which error code?

Answer
$ cargo run --bin q5
error[E0499]: cannot borrow `count` as mutable more than once at a time
 --> src/bin/q5.rs:4:20
  |
3 |     let mut up = || count += 1;
  |                  -- ----- first borrow occurs due to use of `count` in closure
  |                  |
  |                  first mutable borrow occurs here
4 |     let mut down = || count -= 1;
  |                    ^^ ----- second borrow occurs due to use of `count` in closure
  |                    |
  |                    second mutable borrow occurs here
5 |     up();
  |     -- first borrow later used here

E0499. Each closure holds a &mut count for as long as it is used, and up is still used after down was made. Calling up() before creating down ends the first borrow in time. Taught in Two borrows at once and What a closure captures.

Rustq6.rs
fn main() {
let names = vec![String::from("Ada"), String::from("Grace")];
let first = names[0];
println!("{first} and {}", names.len());
}

Does it compile? If not, which error code, and what does the compiler suggest?

Answer
$ cargo run --bin q6
error[E0507]: cannot move out of index of `Vec<String>`
 --> src/bin/q6.rs:3:17
  |
3 |     let first = names[0];
  |                 ^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
  |
help: consider borrowing here
  |
3 |     let first = &names[0];
  |                 +
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let first = names[0].clone();
  |                         ++++++++

E0507. Indexing borrows the Vec, and a String cannot be moved out of a borrow. The two suggestions are a reference (&names[0]) or a copy of the text (.clone()). Taught in Moving out of a borrow.

Rustsrc/bin/q7.rs
use std::cell::RefCell;
use std::rc::Rc;
struct Node {
name: &'static str,
parent: RefCell<Option<Rc<Node>>>,
children: RefCell<Vec<Rc<Node>>>,
}
impl Drop for Node {
fn drop(&mut self) {
println!("drop {}", self.name);
}
}
fn node(name: &'static str) -> Rc<Node> {
Rc::new(Node {
name,
parent: RefCell::new(None),
children: RefCell::new(Vec::new()),
})
}
fn main() {
let root = node("root");
let leaf = node("leaf");
root.children.borrow_mut().push(Rc::clone(&leaf));
*leaf.parent.borrow_mut() = Some(Rc::clone(&root));
println!(
"root owners: {}, leaf owners: {}",
Rc::strong_count(&root),
Rc::strong_count(&leaf)
);
}

First predict the output. Then keep children as it is (the parent owns its children) and change the parent field, so that drop root and drop leaf both print.

Answer
terminal
$ cargo run --bin q7
root owners: 2, leaf owners: 2

root and leaf own each other, so each count is 2. At the closing brace the variables go and the counts fall to 1, never 0, so no drop runs. Keep children as an owner and make parent the Weak. That means the field’s type, its starting value and the line that sets it:

Rustq7.rs
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!("drop {}", self.name);
}
}
fn node(name: &'static str) -> Rc<Node> {
Rc::new(Node {
name,
parent: RefCell::new(Weak::new()),
children: RefCell::new(Vec::new()),
})
}
fn main() {
let root = node("root");
let leaf = node("leaf");
root.children.borrow_mut().push(Rc::clone(&leaf));
*leaf.parent.borrow_mut() = Rc::downgrade(&root);
println!(
"root owners: {}, leaf owners: {}",
Rc::strong_count(&root),
Rc::strong_count(&leaf)
);
}
$ cargo run --bin q7
root owners: 1, leaf owners: 2
drop root
drop leaf

A Weak is not an owner, so root has one. The leaf variable goes first (2 to 1). Then root reaches 0 and prints, and dropping its children takes leaf to 0. Taught in Cycles and Weak<T>.