Lesson 59 of 94
Drop, Deref & Cow
Box, Rc, String, and Vec all feel like built-in magic, but they're ordinary library types built on two traits you can implement yourself:
Dropruns code when a value goes away. It is howBoxfrees memory, howRcdecrements its count, and how aMutexGuardunlocks.Dereflets a type act like a reference to something else. It is why*boxedworks, whyStringhas everystrmethod, and why&Stringcan be passed where&stris expected.
The third topic, Cow ("clone on write"), is a smart pointer for data that is usually borrowed but sometimes needs to be owned, and avoids allocating when it doesn't have to.
Part 1: Drop
1. Drop runs when the owner goes out of scope
You met Drop in Deriving Standard Traits. Here is the mental model again: the compiler inserts a call to drop at the end of the owner's scope, in reverse declaration order.
struct Resource(&'static str);
impl Drop for Resource {
fn drop(&mut self) {
println!("release {}", self.0);
}
}
fn main() {
let _db = Resource("database connection");
let _file = Resource("log file");
println!("working...");
}working...
release log file
release database connection2. Moving a value moves the responsibility to drop it
The value is dropped exactly once, by its final owner.
struct Lease(&'static str);
impl Drop for Lease {
fn drop(&mut self) {
println!("lease on {} returned", self.0);
}
}
fn keep(lease: Lease) -> Lease {
println!(" inspecting {}", lease.0);
lease
}
fn consume(lease: Lease) {
println!(" consuming {}", lease.0);
} // dropped here
fn main() {
let a = Lease("server-1");
let a = keep(a);
consume(a);
println!("after consume");
let _b = Lease("server-2");
println!("end of main");
} inspecting server-1
consuming server-1
lease on server-1 returned
after consume
end of main
lease on server-2 returned3. RAII guards: cleanup that can't be forgotten
A guard is a value whose Drop undoes something its constructor did. The cleanup happens on every exit path: normal return, early return, ?, or even a panic.
struct Indent<'a> {
depth: &'a mut usize,
}
impl<'a> Indent<'a> {
fn new(depth: &'a mut usize) -> Self {
*depth += 1;
Indent { depth }
}
}
impl Drop for Indent<'_> {
fn drop(&mut self) {
*self.depth -= 1;
}
}
fn log(depth: usize, msg: &str) {
println!("{}{msg}", " ".repeat(depth));
}
fn process(depth: &mut usize, fail: bool) -> Result<(), String> {
log(*depth, "begin order");
let guard = Indent::new(depth);
log(*guard.depth, "validate");
if fail {
return Err(String::from("invalid order")); // guard still restores the depth
}
log(*guard.depth, "charge");
Ok(())
}
fn main() {
let mut depth = 0;
println!("{:?}", process(&mut depth, false));
println!("{:?}", process(&mut depth, true));
println!("depth back to {depth}");
}begin order
validate
charge
Ok(())
begin order
validate
Err("invalid order")
depth back to 0The cleanup is applied automatically by the type rather than by each caller having to remember it.
4. A timing guard
A guard that records when it was created and reports when it's dropped measures a scope's duration. The clock is passed in to keep the output deterministic.
use std::cell::Cell;
struct Span<'a> {
name: &'static str,
start: u64,
clock: &'a Cell<u64>,
}
impl Drop for Span<'_> {
fn drop(&mut self) {
println!("{} took {} ms", self.name, self.clock.get() - self.start);
}
}
fn main() {
let clock = Cell::new(0);
let _total = Span { name: "request", start: clock.get(), clock: &clock };
{
let _db = Span { name: "db query", start: clock.get(), clock: &clock };
clock.set(clock.get() + 35);
}
clock.set(clock.get() + 12);
}db query took 35 ms
request took 47 ms5. Dropping early with drop
std::mem::drop(value) takes ownership and drops the value right away, releasing a resource before the end of the scope. It's commonly used to unlock a mutex early.
struct Lock(&'static str);
impl Drop for Lock {
fn drop(&mut self) {
println!("unlocked {}", self.0);
}
}
fn main() {
let guard = Lock("inventory");
println!("updating inventory");
drop(guard); // unlock now...
println!("sending confirmation email (slow, no lock needed)"); // ...not after this
}updating inventory
unlocked inventory
sending confirmation email (slow, no lock needed)6. Drop order of struct fields and collections
A struct drops itself first, then its fields in declaration order. A Vec drops its elements front to back.
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!("drop {}", self.0);
}
}
struct Server {
_listener: Noisy,
_pool: Noisy,
}
fn main() {
let _s = Server { _listener: Noisy("listener"), _pool: Noisy("pool") };
let _v = vec![Noisy("v[0]"), Noisy("v[1]")];
println!("end of main");
}end of main
drop v[0]
drop v[1]
drop listener
drop poolLocal variables drop in reverse order (so _v before _s), while fields and elements drop in forward order. When one field depends on another, declare it first so it is dropped first.
7. mem::forget: skipping Drop
std::mem::forget takes ownership and deliberately never runs drop. It's safe (leaking memory isn't undefined behavior) but rarely what you want.
struct TempFile(&'static str);
impl Drop for TempFile {
fn drop(&mut self) {
println!("deleting {}", self.0);
}
}
fn main() {
let keep = TempFile("report-final.pdf");
let cleanup = TempFile("scratch.tmp");
std::mem::forget(keep); // this file will not be deleted
drop(cleanup);
println!("done");
}deleting scratch.tmp
done8. ManuallyDrop: taking control of when drop runs
ManuallyDrop<T> wraps a value so it is not dropped automatically. You decide if and when. It's mostly used in low-level code (see Unsafe Rust).
use std::mem::ManuallyDrop;
struct Buffer(&'static str);
impl Drop for Buffer {
fn drop(&mut self) {
println!("freed {}", self.0);
}
}
fn main() {
let a = ManuallyDrop::new(Buffer("a"));
let b = Buffer("b");
println!("a is still usable: {}", a.0);
let inner: Buffer = ManuallyDrop::into_inner(a); // back to normal drop behavior
drop(inner);
drop(b);
}a is still usable: a
freed a
freed bPart 2: Deref
9. Implementing Deref for a wrapper
Deref defines what *value means and lets the wrapper be used like the wrapped type. Here a newtype gets all of Vec's read methods for free.
use std::ops::Deref;
struct SortedVec(Vec<i32>);
impl SortedVec {
fn from(mut v: Vec<i32>) -> Self {
v.sort();
SortedVec(v)
}
}
impl Deref for SortedVec {
type Target = Vec<i32>;
fn deref(&self) -> &Vec<i32> {
&self.0
}
}
fn main() {
let s = SortedVec::from(vec![5, 1, 4, 2]);
println!("{:?} len {} first {:?}", *s, s.len(), s.first());
println!("contains 4: {}", s.contains(&4));
println!("binary_search 5: {:?}", s.binary_search(&5));
}[1, 2, 4, 5] len 4 first Some(1)
contains 4: true
binary_search 5: Ok(3)Notice there's no DerefMut: callers can read the vector but can't push unsorted values into it, which protects the invariant.
10. Deref coercion: &Wrapper works where &Target is expected
When a &T is passed where &U is expected and T: Deref<Target = U>, Rust inserts the dereference automatically. That is why &String works as &str and &Vec<T> as &[T]. It chains through several levels.
fn total(values: &[i32]) -> i32 {
values.iter().sum()
}
fn shout(s: &str) -> String {
s.to_uppercase()
}
fn main() {
let v: Vec<i32> = vec![1, 2, 3];
let boxed: Box<Vec<i32>> = Box::new(vec![4, 5]);
let s = String::from("deref");
let rc_s = std::rc::Rc::new(String::from("chains"));
println!("{} {}", total(&v), total(&boxed)); // &Vec -> &[i32], &Box<Vec> -> &Vec -> &[i32]
println!("{} {}", shout(&s), shout(&rc_s)); // &String -> &str, &Rc<String> -> &String -> &str
}6 9
DEREF CHAINS11. DerefMut for mutable access
Implementing DerefMut as well allows &mut Wrapper to be used as &mut Target.
use std::ops::{Deref, DerefMut};
struct Tracked<T> {
value: T,
writes: u32,
}
impl<T> Deref for Tracked<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
impl<T> DerefMut for Tracked<T> {
fn deref_mut(&mut self) -> &mut T {
self.writes += 1;
&mut self.value
}
}
fn main() {
let mut names = Tracked { value: Vec::new(), writes: 0 };
names.push("ana");
names.push("ben");
names.sort();
println!("{:?} len {} (mutable accesses: {})", *names, names.len(), names.writes);
}["ana", "ben"] len 2 (mutable accesses: 3)12. Implementing your own smart pointer
With Deref, DerefMut, and Drop, you can build a simple smart pointer: here, a box that logs its lifecycle.
use std::ops::{Deref, DerefMut};
struct LoggedBox<T: std::fmt::Debug> {
inner: Box<T>,
}
impl<T: std::fmt::Debug> LoggedBox<T> {
fn new(value: T) -> Self {
println!("alloc {:?}", value);
LoggedBox { inner: Box::new(value) }
}
}
impl<T: std::fmt::Debug> Deref for LoggedBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: std::fmt::Debug> DerefMut for LoggedBox<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.inner
}
}
impl<T: std::fmt::Debug> Drop for LoggedBox<T> {
fn drop(&mut self) {
println!("free {:?}", self.inner);
}
}
fn main() {
let mut scores = LoggedBox::new(vec![10, 20]);
scores.push(30);
println!("sum {}", scores.iter().sum::<i32>());
}alloc [10, 20]
sum 60
free [10, 20, 30]13. Don't use Deref for inheritance
It's tempting to use Deref to make Manager "extend" Employee. This is considered an anti-pattern: method lookup becomes surprising, and it doesn't work for trait-based code. Use explicit delegation or composition instead.
struct Employee {
name: String,
salary: u32,
}
impl Employee {
fn describe(&self) -> String {
format!("{} earns {}", self.name, self.salary)
}
}
struct Manager {
employee: Employee, // composition, not Deref
reports: Vec<String>,
}
impl Manager {
fn describe(&self) -> String {
format!("{} and manages {}", self.employee.describe(), self.reports.join(", "))
}
}
fn main() {
let m = Manager { employee: Employee { name: String::from("Grace"), salary: 12_000 }, reports: vec![String::from("Ana"), String::from("Ben")] };
println!("{}", m.describe());
}Grace earns 12000 and manages Ana, BenReserve Deref for types that really are "a pointer to" or "a view of" their target, like Box, String, and the SortedVec above.
Part 3: Cow
14. Cow: borrowed or owned
Cow<'a, B> ("clone on write") is an enum with two variants: Borrowed(&'a B) and Owned(B::Owned). For text, that's Cow<str>: either a &str or a String. It lets a function return borrowed data when nothing changed, and allocate only when it must.
use std::borrow::Cow;
fn normalize_spaces(input: &str) -> Cow<'_, str> {
if input.contains(" ") {
let mut out = String::with_capacity(input.len());
let mut prev_space = false;
for c in input.chars() {
if !(c == ' ' && prev_space) {
out.push(c);
}
prev_space = c == ' ';
}
Cow::Owned(out)
} else {
Cow::Borrowed(input) // nothing to fix: no allocation
}
}
fn main() {
for text in ["already clean", "too many spaces"] {
let result = normalize_spaces(text);
let kind = match &result {
Cow::Borrowed(_) => "borrowed",
Cow::Owned(_) => "owned (allocated)",
};
println!("{result:?}: {kind}");
}
}"already clean": borrowed
"too many spaces": owned (allocated)In a hot loop where most input is already clean, this avoids nearly every allocation.
15. Using a Cow like the data it holds
Cow<str> derefs to str, so you can call string methods on it and pass it where &str is expected.
use std::borrow::Cow;
fn greeting(name: &str) -> Cow<'_, str> {
if name.is_empty() { Cow::Borrowed("stranger") } else { Cow::Borrowed(name) }
}
fn main() {
let g = greeting("");
println!("Hello, {}! ({} chars, uppercase {})", g, g.len(), g.to_uppercase());
}Hello, stranger! (8 chars, uppercase STRANGER)16. to_mut and into_owned
to_mut() gives a &mut to owned data, cloning the borrowed data first if necessary (that's the "clone on write"). into_owned() returns the owned value, cloning only if it was borrowed.
use std::borrow::Cow;
fn main() {
let original = "hello";
let mut text: Cow<str> = Cow::Borrowed(original);
println!("before: borrowed = {}", matches!(text, Cow::Borrowed(_)));
text.to_mut().push_str(", world"); // clones "hello" into a String, then appends
println!("after: {text}, borrowed = {}", matches!(text, Cow::Borrowed(_)));
let owned: String = text.into_owned(); // already owned: no extra clone
println!("{owned}");
}before: borrowed = true
after: hello, world, borrowed = false
hello, world17. Cow<[T]> for slices
Cow works for any type that has a borrowed and an owned form, such as [T] and Vec<T>.
use std::borrow::Cow;
fn without_negatives(values: &[i32]) -> Cow<'_, [i32]> {
if values.iter().any(|&v| v < 0) {
Cow::Owned(values.iter().copied().filter(|&v| v >= 0).collect())
} else {
Cow::Borrowed(values)
}
}
fn main() {
let clean = [1, 2, 3];
let dirty = [4, -1, 5, -9];
for data in [&clean[..], &dirty[..]] {
let r = without_negatives(data);
println!("{:?} (allocated: {})", r, matches!(r, Cow::Owned(_)));
}
}[1, 2, 3] (allocated: false)
[4, 5] (allocated: true)18. Cow in struct fields
A struct can hold a Cow field, so it can be built from borrowed data cheaply or own its data when necessary, with one type.
use std::borrow::Cow;
#[derive(Debug)]
struct LogEntry<'a> {
level: &'static str,
message: Cow<'a, str>,
}
fn entry<'a>(level: &'static str, message: &'a str) -> LogEntry<'a> {
if message.contains('\n') {
LogEntry { level, message: Cow::Owned(message.replace('\n', "\\n")) }
} else {
LogEntry { level, message: Cow::Borrowed(message) }
}
}
fn main() {
let raw = ["user logged in", "stack trace:\n at main\n at run"];
for m in raw {
let e = entry("INFO", m);
println!("[{}] {} ({})", e.level, e.message, if matches!(e.message, Cow::Owned(_)) { "escaped" } else { "as is" });
}
}[INFO] user logged in (as is)
[INFO] stack trace:\n at main\n at run (escaped)19. Cow with 'static defaults
Cow<'static, str> can hold either a string literal or a runtime String, a common type for configuration and error messages.
use std::borrow::Cow;
fn status_message(code: u16) -> Cow<'static, str> {
match code {
200 => Cow::Borrowed("OK"),
404 => Cow::Borrowed("Not Found"),
other => Cow::Owned(format!("Unknown status {other}")),
}
}
fn main() {
let messages: Vec<Cow<'static, str>> = [200, 418, 404].iter().map(|&c| status_message(c)).collect();
println!("{:?}", messages);
}["OK", "Unknown status 418", "Not Found"]20. String::from_utf8_lossy returns a Cow
The standard library uses Cow where it can: valid UTF-8 is borrowed as is, and only invalid input is copied and repaired.
use std::borrow::Cow;
fn main() {
let valid = b"hello";
let invalid = b"hel\xFFlo";
for bytes in [&valid[..], &invalid[..]] {
let text = String::from_utf8_lossy(bytes);
println!("{text} (copied: {})", matches!(text, Cow::Owned(_)));
}
}hello (copied: false)
hel�lo (copied: true)21. Realistic example: an HTML escaper that rarely allocates
Most text contains no special characters. Returning Cow means those cases cost nothing.
use std::borrow::Cow;
fn escape_html(input: &str) -> Cow<'_, str> {
if !input.contains(['<', '>', '&', '"']) {
return Cow::Borrowed(input);
}
let mut out = String::with_capacity(input.len() + 16);
for c in input.chars() {
match c {
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'&' => out.push_str("&"),
'"' => out.push_str("""),
_ => out.push(c),
}
}
Cow::Owned(out)
}
fn main() {
let comments = ["Great post!", "I <3 Rust & Java", "Use \"cargo run\""];
let mut allocations = 0;
for c in comments {
let escaped = escape_html(c);
if let Cow::Owned(_) = escaped {
allocations += 1;
}
println!("<p>{escaped}</p>");
}
println!("{allocations} of {} comments needed a new string", comments.len());
}<p>Great post!</p>
<p>I <3 Rust & Java</p>
<p>Use "cargo run"</p>
2 of 3 comments needed a new string22. Realistic example: a scoped configuration override
A guard temporarily changes a setting and restores the previous value when dropped, even if the code in between returns early. Test frameworks use this pattern for environment variables and feature flags.
use std::cell::RefCell;
struct Settings {
values: RefCell<Vec<(&'static str, String)>>,
}
impl Settings {
fn get(&self, key: &str) -> String {
self.values.borrow().iter().find(|(k, _)| *k == key).map(|(_, v)| v.clone()).unwrap_or_default()
}
fn set(&self, key: &'static str, value: String) -> String {
let mut values = self.values.borrow_mut();
let slot = values.iter_mut().find(|(k, _)| *k == key).expect("known key");
std::mem::replace(&mut slot.1, value)
}
fn override_for_scope<'a>(&'a self, key: &'static str, value: &str) -> Override<'a> {
let previous = self.set(key, value.to_string());
Override { settings: self, key, previous: Some(previous) }
}
}
struct Override<'a> {
settings: &'a Settings,
key: &'static str,
previous: Option<String>,
}
impl Drop for Override<'_> {
fn drop(&mut self) {
if let Some(prev) = self.previous.take() {
self.settings.set(self.key, prev);
}
}
}
fn main() {
let settings = Settings { values: RefCell::new(vec![("log_level", String::from("info"))]) };
println!("before: {}", settings.get("log_level"));
{
let _debug = settings.override_for_scope("log_level", "debug");
println!("inside: {}", settings.get("log_level"));
}
println!("after: {}", settings.get("log_level"));
}before: info
inside: debug
after: info23. Realistic example: a pooled connection that returns itself
When a pooled connection is dropped, it goes back to the pool instead of being closed. Callers just use it and let it go out of scope, exactly like a JDBC connection pool, but without needing to call close().
use std::cell::RefCell;
use std::ops::Deref;
struct Pool {
idle: RefCell<Vec<String>>,
}
struct PooledConn<'a> {
conn: Option<String>,
pool: &'a Pool,
}
impl Pool {
fn get(&self) -> Option<PooledConn<'_>> {
let conn = self.idle.borrow_mut().pop()?;
Some(PooledConn { conn: Some(conn), pool: self })
}
}
impl Deref for PooledConn<'_> {
type Target = String;
fn deref(&self) -> &String {
self.conn.as_ref().unwrap()
}
}
impl Drop for PooledConn<'_> {
fn drop(&mut self) {
if let Some(c) = self.conn.take() {
println!(" returning {c} to the pool");
self.pool.idle.borrow_mut().push(c);
}
}
}
fn main() {
let pool = Pool { idle: RefCell::new(vec![String::from("conn-1"), String::from("conn-2")]) };
{
let a = pool.get().unwrap();
let b = pool.get().unwrap();
println!("using {} and {}", *a, *b);
println!("third connection available: {}", pool.get().is_some());
}
println!("idle again: {:?}", pool.idle.borrow());
}using conn-2 and conn-1
third connection available: false
returning conn-1 to the pool
returning conn-2 to the pool
idle again: ["conn-1", "conn-2"]Summary
Dropruns cleanup exactly once, when a value's last owner goes away: reverse order for locals, declaration order for fields. RAII guards use it to make cleanup impossible to forget.drop(x)releases early;mem::forgetandManuallyDropskip or defer drop for special cases.Deref/DerefMutmake a type act like a reference to its target, and enable deref coercion (&String→&str,&Box<Vec<T>>→&[T]). Use it for pointer-like wrappers, not for inheritance.Cow<'a, T>holds borrowed or owned data. Return it from functions that usually don't need to allocate;to_mutandinto_ownedclone only when needed.
This completes the Smart Pointers section. Next: OOP Concepts in Rust, the start of OOP & Design Patterns, where traits, enums, closures, and smart pointers come together.
