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_countwhile your ownupgrade()handle is alive: it is one higher than the owners you meant to count. - Building a value before checking you can place it: a
Noisycreated and then thrown away still prints itsdropline. - Making the parent link an
Rc: the cycle means nothing is freed and nodropline prints. - Expecting
get_mutortry_unwrapto succeed while another handle exists: you getNoneandErr.
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.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…