Step 3 of 7
Deref and Drop: what makes a pointer smart
Two traits give smart pointers their behavior. Deref makes a type usable like a reference to what it holds. Drop runs cleanup code when a value goes away. You met both briefly in the std traits module; here's how they work together.
Deref and deref coercion
impl Deref for MyType { type Target = T; fn deref(&self) -> &T } says "a &MyType can be used as a &T". Then two things happen automatically:
*valueworks, and method calls look through the pointer:boxed_string.len()findsString::len.- Deref coercion: when you pass
&MyTypewhere a&Tis expected, Rust inserts thederefcalls for you, as many steps as needed.
use std::ops::Deref;
struct Username(String);
impl Deref for Username {
type Target = String;
fn deref(&self) -> &String {
&self.0
}
}
fn greet(name: &str) {
println!("Welcome, {name}!");
}
fn main() {
let user = Username(String::from("ferris"));
println!("{} letters", user.len()); // String::len, through Deref
greet(&user); // &Username -> &String -> &str
let boxed = Box::new(String::from("corro"));
greet(&boxed); // &Box<String> -> &String -> &str
}
6 letters
Welcome, ferris!
Welcome, corro!
Box<T> implements Deref<Target = T>, String implements Deref<Target = str>, and Vec<T> implements Deref<Target = [T]>. That's why a &String works where a &str is wanted and a &Vec<T> where a &[T] is. Implement Deref for wrapper types that are a kind of pointer or container; the Rust API guidelines warn against using it to fake inheritance between unrelated structs.
Drop: cleanup at the end of a scope
When a value goes out of scope, Rust calls its drop method (if it has one), then drops its fields. That's how a Box frees its heap memory, a File closes, and a lock is released. This pattern of "cleanup tied to a value's lifetime" is called RAII. The order is fixed, so you can rely on it:
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!("drop {}", self.0);
}
}
fn main() {
let _a = Noisy("a");
let b = Noisy("b");
{
let _inner = Noisy("inner");
println!("leaving inner scope");
}
drop(b); // drop right now
let _c = Noisy("c");
println!("end of main");
}
leaving inner scope
drop inner
drop b
end of main
drop c
drop a
- Values are dropped at the end of their scope, in reverse order of declaration:
_c(declared last) goes before_a. Later values may depend on earlier ones, so they go first. drop(b)drops a value early. It's an ordinary function from the prelude (std::mem::drop) that takes ownership and does nothing, so the value is dropped when the function ends.- The fields of a struct are dropped after the struct's own
drop, in the order they're declared. Elements of aVecare dropped first to last.
Common mistakes
- Calling
.drop()yourself:b.drop()iserror[E0040]: explicit use of destructor method. Usedrop(b); Rust must be sure it never runs twice. let _ = Noisy("x");drops the new value immediately. The pattern_doesn't bind anything, so a guard created that way is gone on the same line. Writelet _guard = ...to keep it alive until the end of the scope.- Using a value after
drop(x):droptook ownership, so it'serror[E0382]: borrow of moved value.
Your task
Implement Drop for Connection so dropping one prints closing <name>. Then finish main: it opens db, then cache, closes db early with drop, prints working, and lets cache close at the end. The output must be exactly:
opened db
opened cache
closing db
working
closing cache
Challenge 2
A SortedScores always keeps its scores sorted, so it must never hand out a &mut to its inner Vec. Implement:
SortedScores::new(scores: Vec<u32>) -> SortedScores: sorts the scores and wraps them.DerefforSortedScoreswithTarget = [u32], so callers can use every read-only slice method (len,first,last,iter, indexing) without you writing them.
Don't implement DerefMut: that's what keeps the scores sorted.
Challenge 3
Write a tracing helper. A Span prints enter <name> when it's created with Span::new(name) and exit <name> when it's dropped. Then write main so the program prints exactly:
enter request
enter parse
exit parse
enter save
exit save
exit request
Create each span once and let scopes (or drop) end them. Don't print the lines with println! in main.