Skip to content
Lesson 1 of 7

Step 1 of 5 · Reading · ~3 min

Learn

Smart Pointers and Interior Mutability

Deref, Drop and Weak: Building Smart Pointers

You will be able to make your own type behave like a pointer with Deref and Drop, predict the order in which values are freed, and break reference cycles with Weak.

The intermediate Smart Pointers lesson used Box, Rc and RefCell; this one opens them up and shows what Rc offers beyond clone.

Deref: * and coercion

A smart pointer is a struct that owns something and implements Deref (and DerefMut for writes). *w means *w.deref():

struct Wrapper<T>(T);
impl<T> Deref for Wrapper<T> {
    type Target = T;
    fn deref(&self) -> &T { &self.0 }
}
impl<T> DerefMut for Wrapper<T> {
    fn deref_mut(&mut self) -> &mut T { &mut self.0 }
}
fn shout(s: &str) -> String { s.to_uppercase() }

let mut w = Wrapper(String::from("hi"));
println!("{}", shout(&w));      // HI
w.push('!');                    // String::push via DerefMut
println!("{} {}", *w, w.len()); // hi! 3

shout(&w) compiles because of deref coercion: where a &U is expected and you hold a &T, the compiler inserts deref calls, here &Wrapper<String> to &String to &str. Without the Deref impl, shout(&w) is error[E0308]: mismatched types.

Drop: what runs when a value goes away

Drop::drop(&mut self) runs once, just before the fields are freed:

struct Noisy(&'static str);
impl Drop for Noisy {
    fn drop(&mut self) { println!("drop {}", self.0); }
}
struct Pair { first: Noisy, second: Noisy }

let a = Noisy("a");
let _p = Pair { first: Noisy("p.first"), second: Noisy("p.second") };
let _v = vec![Noisy("v0"), Noisy("v1")];
let _b = Noisy("b");
drop(a);                  // moves `a` into a function: freed now
Noisy("temp");            // temporary: dropped at end of statement
let _ = Noisy("ignored"); // `_` binds nothing: dropped now
println!("end");
drop a
drop temp
drop ignored
end
drop b
drop v0
drop v1
drop p.first
drop p.second

Locals drop in reverse declaration order; struct fields top to bottom; Vec elements front to back. n.drop() is error[E0040]: explicit use of destructor method; call drop(n). mem::forget(n) and process::exit skip drops.

Rc beyond clone

let mut cfg = Rc::new(String::from("v1"));
println!("{:?}", Rc::get_mut(&mut cfg));     // Some("v1")
let other = Rc::clone(&cfg);
println!("{:?}", Rc::get_mut(&mut cfg));     // None
Rc::make_mut(&mut cfg).push_str("-edit");    // shared, so it clones first
println!("{} {}", cfg, other);               // v1-edit v1
if let Ok(s) = Rc::try_unwrap(other) {
    println!("owned {}", s);                 // owned v1
}

get_mut gives &mut T only when no other Rc or Weak exists. make_mut is copy-on-write: in place if you are the only owner, otherwise it clones the value and moves your handle to the copy. try_unwrap returns Ok(value) at strong count 1, else Err(rc) with your handle back.

Weak: a pointer that does not own

Rc::downgrade(&rc) makes a Weak<T>: the strong count stays put and Rc::weak_count rises. weak.upgrade() returns Option<Rc<T>>, Some while a strong handle exists. It builds a new strong handle, so the count is one higher while you hold it:

let strong = Rc::new(String::from("session-42"));
let weak = Rc::downgrade(&strong);
println!("{} {}", Rc::strong_count(&strong), Rc::weak_count(&strong)); // 1 1
{
    let _held = weak.upgrade().unwrap();
    println!("{}", Rc::strong_count(&strong));  // 2
}
drop(strong);
println!("{:?}", weak.upgrade());               // None

Two nodes x and y holding Rcs to each other never reach count 0: the counts print 2 2 and no drop line ever appears. With Weak links they print 1 1, then drop y, drop x. For trees: the parent owns its children with Rc, a child points back with Weak.

Common mistakes

  • Reading Rc::strong_count while your own upgrade() handle is alive: it is one higher than the owners you meant to count.
  • Building a value before checking you can place it: a Noisy created and then thrown away still prints its drop line.
  • Making the parent link an Rc: the cycle means nothing is freed and no drop line prints.
  • Expecting get_mut or try_unwrap to succeed while another handle exists: you get None and Err.

Your exercise

Finish the family forest in the starter: Node prints drop <name> when freed, and every lookup upgrades a Weak handle. The usual slip is a count that includes your own lookup handle.

Up nextCell, RefCell and Interior MutabilitySmart Pointers and Interior Mutability

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…