RustArena

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:

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

Common mistakes

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:

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.

Previous: Trees with Box Next: Rc<T>: shared ownership