Deref and Drop Traits
Two traits underpin how smart pointers work in Rust: Deref controls what happens
when you write *pointer to dereference a value, and Drop controls what happens
when a value goes out of scope. Together they make smart pointers feel like regular
references while still performing their behind-the-scenes resource management.
The Deref Trait
Deref is what allows you to write *smart_pointer and reach the inner value.
Any type that implements Deref can be used in many of the same places as a plain
reference.
The trait requires one associated type and one method:
pub trait Deref {
type Target;
fn deref(&self) -> &Self::Target;
}When you write *x, Rust silently rewrites it as *(x.deref()). The explicit
* operator and the deref() method always go together — you never call
deref() manually in normal code.
Implementing Deref on a Custom Type
Here is a minimal MyBox<T> that wraps a value and implements Deref so it behaves
like the standard Box<T> when dereferenced.
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> Self {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0 // return a reference to the inner value
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y); // *y => *(y.deref()) => *(&5) => 5
println!("x = {}, *y = {}", x, *y);
}x = 5, *y = 5
deref() automatically whenever you write *y. You never invoke y.deref() directly in normal code — that is the compiler's job.Deref Coercions
A deref coercion is an automatic, implicit conversion that Rust applies when a
reference of one type is used where a reference of a different type is expected. The
compiler chains .deref() calls as many times as needed to make the types match.
The three most common coercions:
From | To | Why it works |
|---|---|---|
&String | &str | String implements Deref<Target = str> |
&Vec<T> | &[T] | Vec<T> implements Deref<Target = [T]> |
&Box<T> | &T | Box<T> implements Deref<Target = T> |
fn print_str(s: &str) {
println!("{}", s);
}
fn sum_slice(nums: &[i32]) -> i32 {
nums.iter().sum()
}
fn main() {
// &String coerces to &str automatically
let s = String::from("hello, deref coercion!");
print_str(&s); // no &s[..] needed
// &Vec<i32> coerces to &[i32] automatically
let v = vec![1, 2, 3, 4, 5];
println!("sum: {}", sum_slice(&v));
// &Box<String> coerces to &String, then to &str — two steps chained
let boxed = Box::new(String::from("boxed string"));
print_str(&boxed);
}hello, deref coercion! sum: 15 boxed string
&s[..] to convert a &String to &str, or &v[..] for a slice. Just pass &s and &v — the compiler chains the coercions automatically.DerefMut — Mutable Deref
DerefMut is the mutable counterpart to Deref. It enables *mut access and
allows mutable deref coercions. Rust applies it when the source is &mut and the
target is also expected as &mut.
use std::ops::{Deref, DerefMut};
struct Wrapper(String);
impl Deref for Wrapper {
type Target = String;
fn deref(&self) -> &String { &self.0 }
}
impl DerefMut for Wrapper {
fn deref_mut(&mut self) -> &mut String { &mut self.0 }
}
fn append(s: &mut String) {
s.push_str(" world");
}
fn main() {
let mut w = Wrapper(String::from("hello"));
append(&mut w); // &mut Wrapper coerces to &mut String via DerefMut
println!("{}", *w); // "hello world"
}hello world
The Drop Trait
Drop is how Rust implements RAII (Resource Acquisition Is Initialization).
When a value goes out of scope, the compiler automatically inserts a call to its
drop method. This is where file handles close, network connections disconnect, and
allocated memory is freed — all without the programmer having to remember to do it.
The trait has one required method:
pub trait Drop {
fn drop(&mut self);
}Implementing Drop
struct Resource {
name: String,
}
impl Resource {
fn new(name: &str) -> Self {
println!("acquiring: {}", name);
Resource { name: name.to_string() }
}
}
impl Drop for Resource {
fn drop(&mut self) {
println!("releasing: {}", self.name);
}
}
fn main() {
let _a = Resource::new("file handle");
let _b = Resource::new("network connection");
let _c = Resource::new("database lock");
println!("--- all resources in use ---");
} // _c dropped first, _b second, _a last (LIFO order)acquiring: file handle acquiring: network connection acquiring: database lock --- all resources in use --- releasing: database lock releasing: network connection releasing: file handle
Drop Order in Structs
Inside a struct, fields are dropped in the order they are declared — top to bottom.
The struct's own drop method runs first, and then the fields are dropped.
struct Inner { label: &'static str }
struct Outer { first: Inner, second: Inner }
impl Drop for Inner {
fn drop(&mut self) { println!("Inner '{}' dropped", self.label); }
}
impl Drop for Outer {
fn drop(&mut self) { println!("Outer dropped (fields follow)"); }
}
fn main() {
let _o = Outer {
first: Inner { label: "first" },
second: Inner { label: "second" },
};
println!("using Outer");
}using Outer Outer dropped (fields follow) Inner 'first' dropped Inner 'second' dropped
Dropping a Value Early with std::mem::drop
Sometimes you want a value cleaned up before the end of its scope — for example, releasing a mutex lock early to reduce contention, or freeing a large buffer as soon as it is no longer needed.
You cannot call .drop() directly on a value — the compiler forbids it because
doing so would cause a double-free (Rust would call drop once explicitly, then
again automatically at end of scope). Instead use the free function drop(), which
is in the prelude and requires no import.
struct Lock { name: String }
impl Drop for Lock {
fn drop(&mut self) {
println!("lock '{}' released", self.name);
}
}
fn main() {
let lock = Lock { name: String::from("mutex-1") };
println!("lock acquired");
// Release the lock early
drop(lock); // correct — calls Drop and then invalidates 'lock'
println!("lock released early, doing more work...");
// 'lock' is no longer valid here
// println!("{}", lock.name); // ERROR: use of moved value
}lock acquired lock 'mutex-1' released lock released early, doing more work...
.drop() directly on a value — the compiler rejects it to prevent a double-free. Always use the drop(value) free function, which moves the value in and drops it exactly once.Copy and Drop Cannot Both Be Implemented
A type cannot implement both Copy and Drop. This is a deliberate constraint:
Copy types are duplicated by a simple bitwise copy with no special semantics. If
such a type also had Drop, dropping one copy could invalidate another copy's
resources — a use-after-free.
If you implement Drop, the type automatically opts out of Copy.
// Combining Copy and Drop is a compile error:
// #[derive(Copy, Clone)]
// struct Handle { fd: i32 }
// impl Drop for Handle { fn drop(&mut self) { /* close fd */ } }
// The correct approach: only derive Clone (not Copy)
#[derive(Clone)]
struct Handle { fd: i32 }
impl Drop for Handle {
fn drop(&mut self) {
println!("closing fd {}", self.fd);
}
}
fn main() {
let h1 = Handle { fd: 3 };
let h2 = h1.clone(); // explicit clone — not a silent bitwise copy
println!("h1.fd = {}, h2.fd = {}", h1.fd, h2.fd);
} // h2 dropped, then h1 droppedh1.fd = 3, h2.fd = 3 closing fd 3 closing fd 3
Real-World Uses of Drop
File handles —
std::fs::Filecloses the OS file descriptor in itsdropimplementation.Network sockets — TCP streams flush pending data and close the connection when dropped.
Mutex guards —
MutexGuardreleases the lock when it goes out of scope, preventing deadlocks.Database connections — connection pool handles return the connection to the pool in
drop.Custom allocators — memory arenas free their entire backing region when the arena struct is dropped.
Deref and Drop Together — A Complete Smart Pointer
Deref and Drop are exactly what makes Box<T>, Rc<T>, Arc<T>, and
MutexGuard work. Here is a minimal smart pointer that combines both traits:
use std::ops::Deref;
struct SmartPtr<T> {
data: Box<T>,
label: &'static str,
}
impl<T> SmartPtr<T> {
fn new(value: T, label: &'static str) -> Self {
println!("[{}] allocated", label);
SmartPtr { data: Box::new(value), label }
}
}
impl<T> Deref for SmartPtr<T> {
type Target = T;
fn deref(&self) -> &T {
&self.data
}
}
impl<T> Drop for SmartPtr<T> {
fn drop(&mut self) {
println!("[{}] freed", self.label);
}
}
fn print_value(s: &str) {
println!("value: {}", s);
}
fn main() {
let ptr = SmartPtr::new(String::from("hello"), "ptr");
// Deref coercion: &SmartPtr<String> -> &String -> &str
print_value(&ptr);
} // Drop called automatically[ptr] allocated value: hello [ptr] freed
Deref makes smart pointers transparent — you use them just like ordinary references and the compiler inserts the indirection automatically. Drop guarantees cleanup without manual bookkeeping — resources are always released exactly once, in the correct order, at the correct time. These two traits together are the foundation of Rust's memory safety guarantees and the reason Rust can manage resources reliably without a garbage collector.