What makes a pointer smart¶
Smart pointers › What makes a pointer smart
Level: 201 · working knowledge
One line: A smart pointer is a struct that implements Deref, so it can be used like the thing it points at, and usually Drop, so something happens when it goes away — owning the pointee is common, and not part of the definition.
use std::ops::Deref;
struct Loud<T> {
value: Box<T>,
}
impl<T> Deref for Loud<T> {
type Target = T;
fn deref(&self) -> &T { &self.value }
}
impl<T> Drop for Loud<T> {
fn drop(&mut self) { println!("letting go"); }
}
fn main() {
let name = Loud { value: Box::new(String::from("ferris")) };
println!("{}", name.to_uppercase()); // FERRIS — a str method, reached through two Derefs
} // letting go
Deref is the "pointer" half¶
std's Deref ↗ docs say types that implement Deref or DerefMut "are often called smart pointers", and that deref coercion exists for them. Deref gives three things, and section 1 of the run uses each:
| You write | What the compiler does | Rule |
|---|---|---|
*greeting |
calls *Deref::deref(&greeting) |
the * operator |
greeting.to_uppercase() |
derefs until a type with that method turns up: Loud<String> → String → str |
Method resolution |
shout(&greeting) where shout takes &str |
inserts derefs at the call: &Loud<String> → &String → &str |
Coercion |
DerefMut does the same for &mut, which is how greeting.push_str(", world") reaches the String. A type with Deref and no DerefMut hands out only shared access — which is Rc:
use std::rc::Rc;
fn main() {
let shared = Rc::new(String::from("state"));
shared.push_str(" changed");
println!("{shared}");
}
error[E0596]: cannot borrow data in an `Rc` as mutable
--> rc_is_not_deref_mut.rs:5:5
|
5 | shared.push_str(" changed");
| ^^^^^^ cannot borrow as mutable
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<String>`
The ways to write through an Rc are Rc::make_mut ↗, which clones when the value is shared, and a RefCell inside it.
Drop is the "smart" half¶
Section 2 of the run shows the other half: Loud's drop runs at the end of the block that owns it, or earlier at drop(greeting). What a smart pointer does in Drop is its job — Box frees its allocation, Rc decrements a count and frees at zero, a Ref from a RefCell gives the borrow back. Drop, and what RAII buys has the rules for when it runs.
The Book's chapter 15 ↗ puts both halves in its definition: smart pointers are usually structs that implement Deref and Drop.
Each one derefs to something¶
| Smart pointer | Deref::Target |
What its Drop does |
|---|---|---|
Box<T> |
T |
frees the heap allocation |
Rc<T>, Arc<T> |
T |
decrements the strong count; drops T at zero |
String |
str |
frees the byte buffer |
Vec<T> |
[T] |
drops the elements, frees the buffer |
Cow<'_, B> |
B |
drops the owned value, if it holds one |
Ref<'_, T>, RefMut<'_, T> |
T |
gives the RefCell borrow back |
MutexGuard<'_, T> |
T |
unlocks the mutex |
Section 3 of the run reads six of these targets off the types with type_name::<P::Target>(). String and Vec in this table surprise people; the Rust Design Patterns ↗ book calls collections smart pointers for exactly this reason, and the claims page checks what that does and does not mean.
Owning is usual, not required¶
The Book says smart pointers own their data "in many cases". Section 4 shows two that do not. Cow::Borrowed(&text) points into text, which it does not own. The Ref guard from cell.borrow() never owned the 5 inside the RefCell; while it lives, try_borrow_mut fails, and its Drop is what makes the next one succeed.
Weak has no Deref¶
A Weak<T> ↗ points at an Rc's allocation without keeping the value alive, so there may be nothing there to deref to:
use std::rc::Rc;
fn main() {
let strong = Rc::new(7);
let weak = Rc::downgrade(&strong);
println!("{}", *weak);
}
error[E0614]: type `std::rc::Weak<{integer}>` cannot be dereferenced
--> weak_has_no_deref.rs:6:20
|
6 | println!("{}", *weak);
| ^^^^^ can't be dereferenced
You ask with weak.upgrade(), which returns Option<Rc<T>>: Some(7) while an Rc lives and None after the last one is dropped (section 5).
The verified output¶
Verified output of what_makes_a_pointer_smart.rs — regenerated by tools/run_examples.py, never hand-typed.
1. Deref: a struct that acts like a pointer
(*greeting).len() 5 explicit: *greeting is the String
greeting.len() 5 the method search derefs for you
greeting.to_uppercase() HELLO a str method, two derefs away
shout(&greeting) HELLO &Loud<String> coerced to &str at the call
after push_str via DerefMut: "hello, world"
2. Drop: something happens when it goes
*temp + 1 = 42
end of block:
drop: temp lets go of its value
drop(greeting) before the end of main:
drop: greeting lets go of its value
3. std's smart pointers, and what each derefs to
String -> str
Vec<u8> -> [u8]
Box<u8> -> u8
Rc<u64> -> u64
Cow<str> -> str
Ref<i32> -> i32
4. "Owns what it points at" is usually true, and not always
Cow::Borrowed points into `text`, which it does not own: true
while the Ref guard lives, try_borrow_mut is_err: true
after drop(guard), try_borrow_mut is_ok: true
The guard never owned the 5. Its Drop gives the borrow back.
5. Weak: a pointer to shared data with no Deref at all
weak.upgrade() Some(7)
after the last Rc is dropped None
`*weak` does not compile. You ask for an Rc, and may get None.
If you are coming from another language¶
- C++. The name comes from here:
std::unique_ptrisBox,std::shared_ptrisArc(its count is atomic),std::weak_ptrisWeak, and overloadingoperator*andoperator->isDeref. The destructor isDrop. What Rust adds is that*on a moved-fromBoxdoes not compile, where a moved-fromunique_ptris a null pointer at run time. - Python. Every Python reference is already a counted smart pointer — CPython's
Py_INCREFandPy_DECREFareRc::cloneanddrop— andweakref.ref(obj)()returningNoneisWeak::upgradereturningNone. The__enter__/__exit__pair of a context manager is the closest thing to a guard likeReforMutexGuard, withDropmaking thewithimplicit. - Swift and Objective-C. ARC is
Arc-style counting inserted by the compiler, andweakreferences areWeak; Rust makes you chooseBox,RcorArcper value instead of counting everything.
Practice¶
A pointer that counts its reads. Write Counted<T>, a smart pointer that owns a Box<T> and counts every call to its deref. deref takes &self, so decide what the counter's type has to be. Give it an associated function Counted::reads(&c) rather than a method, and say why. Then predict the count after each of name.len(), name.to_uppercase(), takes_str(&name), &*name and &name for a Counted<String>, and check.
Solution
what_makes_a_pointer_smart_kata.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.
//! Kata solution: a smart pointer that counts how often it is dereferenced.
//!
//! rustc --edition 2024 what_makes_a_pointer_smart_kata.rs -o /tmp/wmpsk && /tmp/wmpsk
use std::cell::Cell;
use std::ops::Deref;
/// Owns a value and counts every `deref`. `deref` takes `&self`, so the
/// counter needs a `Cell`: a write through a shared reference.
struct Counted<T> {
value: Box<T>,
reads: Cell<usize>,
}
impl<T> Counted<T> {
fn new(value: T) -> Self {
Counted { value: Box::new(value), reads: Cell::new(0) }
}
/// An associated function, not a method, so that calling it cannot
/// itself go through `deref` — the same choice std makes for
/// `Rc::strong_count(&rc)`.
fn reads(this: &Self) -> usize {
this.reads.get()
}
}
impl<T> Deref for Counted<T> {
type Target = T;
fn deref(&self) -> &T {
self.reads.set(self.reads.get() + 1);
&self.value
}
}
fn takes_str(text: &str) -> usize {
text.len()
}
fn main() {
let name = Counted::new(String::from("ferris"));
let mut seen = Vec::new();
let _ = name.len(); // method search: one deref, Counted -> String
seen.push(("name.len()", Counted::reads(&name)));
let _ = name.to_uppercase(); // Counted -> String (by deref) -> str (by String's own Deref)
seen.push(("name.to_uppercase()", Counted::reads(&name)));
let _ = takes_str(&name); // coercion: &Counted<String> -> &String -> &str
seen.push(("takes_str(&name)", Counted::reads(&name)));
let _ = &*name; // explicit
seen.push(("&*name", Counted::reads(&name)));
let _ = &name; // a reference to the pointer itself: no deref
seen.push(("&name", Counted::reads(&name)));
println!("Reads counted after each line:");
for (line, total) in seen {
println!(" {line:<22} {total}");
}
println!();
println!("Each line that reads through the pointer added one call to deref, and");
println!("`&name`, which only borrows the pointer, added none. The second hop in");
println!("to_uppercase and takes_str, String -> str, is String's Deref, not ours.");
}
Verified output of what_makes_a_pointer_smart_kata.rs — regenerated by tools/run_examples.py, never hand-typed.
Reads counted after each line:
name.len() 1
name.to_uppercase() 2
takes_str(&name) 3
&*name 4
&name 4
Each line that reads through the pointer added one call to deref, and
`&name`, which only borrows the pointer, added none. The second hop in
to_uppercase and takes_str, String -> str, is String's Deref, not ours.
See also¶
- What a smart pointer costs — the allocations, counts and flags behind the
Deref - What a smart pointer is — the outline in Ownership: the family by ownership and threads, and the
Deref-on-a-newtype trap - Smart pointer claims, run — "boiled away", "always owns", "Rc is a raw pointer", checked
- Method resolution — the search
name.len()goes through - Coercion — the call-site rule
takes_str(&name)uses - Step 3: Owned and borrowed are two different types —
String: Deref<Target = str>from theToOwnedside - Raw pointers — the pointer a smart pointer is built on
Sources¶
std's Deref ↗, DerefMut ↗ and Weak ↗; The Book, ch. 15, Smart Pointers ↗, as shipped with 1.98.0; Rust Design Patterns — Collections are smart pointers ↗. The two refusals are rustc 1.98.0.
Po polsku¶
Inteligentny wskaźnik (smart pointer) to struktura, która implementuje Deref — więc można jej używać jak tego, na co wskazuje — i zwykle Drop — więc coś się dzieje, gdy znika. Deref daje operator *, wyszukiwanie metod przez kolejne dereferencje (Loud<String> → String → str) i koercję w miejscu wywołania (&Loud<String> → &str). DerefMut robi to samo dla &mut; Rc go nie ma, dlatego shared.push_str(…) kończy się błędem E0596.
Posiadanie wskazywanej wartości jest częste, ale nie należy do definicji: Cow::Borrowed wskazuje na cudzy String, a strażnik Ref z RefCell niczego nie posiada — jego Drop tylko oddaje pożyczkę. Weak w ogóle nie ma Deref (E0614), bo wartości może już nie być; pytasz przez upgrade() i dostajesz Option<Rc<T>>. String i Vec<T> też są inteligentnymi wskaźnikami w tym sensie: mają Deref (do str i [T]) i Drop.
Szukaj po polsku: inteligentne wskaźniki w Ruście · cecha Deref · koercja dereferencji · rust smart pointer Deref Drop · rust Weak upgrade