Smart pointer claims, run¶
Smart pointers › Claims, run
Level: 201 · a companion to the section
One line: Explanations of smart pointers get the idea right and one word wrong — "boiled away", "always owns", "raw pointers such as Rc", "without fuss" — and each of those words is checked below on rustc 1.98.0, by a program or by the compiler's refusal.
The six claims come from introductions to smart pointers of the kind that circulate in articles and course notes; they are quoted in short, as claims. The last section checks the building blocks Rust in Action names in §6.2.3.
Six claims, run¶
| # | Claim | Verdict | Evidence |
|---|---|---|---|
| 1 | smart pointers are a zero-cost abstraction because they are a compile-time construct the compiler boils away | no — the handle is as small as a raw pointer, but counts, flags and allocations happen at run time | section 1; What a smart pointer costs |
| 2 | String and Vec<T> are smart pointers |
yes, in std's sense: both Deref and Drop. They hold a raw pointer, not a reference to the buffer |
section 2 |
| 3 | a smart pointer owns the data it refers to | usually — Cow::Borrowed owns nothing and a Weak keeps nothing alive |
section 3; What makes a pointer smart |
| 4 | some raw pointers, such as Rc<T>, offer shared ownership |
no — Rc is a smart pointer; Rc::as_ptr is what hands you the raw one |
section 4, and the E0308 below |
| 5 | smart pointers implement Deref, so you can use one in place of its referent without fuss |
for method calls and coercions; operators and patterns still need the *, and Weak has no Deref |
section 5, E0369 and E0308 below |
| 6 | Rc is single-threaded |
yes, and the compiler enforces it: Rc<T> is not Send |
section 6, E0277 below |
The verified output¶
Verified output of smart_pointer_claims_checked.rs — regenerated by tools/run_examples.py, never hand-typed.
1. "A compile-time construct the compiler boils away"
strong_count before clone 1, after 2
RefCell borrow_mut while borrowed: Err(BorrowMutError)
A count that changes and a borrow refused at run time are state the
program carries. The allocations are counted on the costs page.
2. "String and Vec<T> are smart pointers"
String derefs to str, Vec<u8> to [u8]
needs_drop: String true, Vec<u8> true
String: 'static true, Vec<u8>: 'static true
Deref and Drop: smart pointers in the sense std's Deref docs use.
Neither has a lifetime parameter, so the buffer is not held by a
reference: std stores a raw pointer (a NonNull) and frees it in Drop.
3. "A smart pointer owns the data it refers to"
Cow::Borrowed(&text) is Borrowed: true
a Weak outliving its value: upgrade() = None
Cow::Borrowed owns nothing and a Weak keeps nothing alive; The Book
says smart pointers own their data "in many cases".
4. "Some raw pointers, such as Rc<T>, offer shared ownership"
Rc::as_ptr(&counted) is a *const u8
needs_drop: Rc<u8> true, *const u8 false
size_of Option<Rc<u8>> 8, Option<*const u8> 16
Rc is a smart pointer built on a NonNull. A raw pointer is what
Rc::as_ptr hands you: no count, no drop, and nullable.
5. "Use a smart pointer in place of its referent without fuss"
boxed.checked_add(1) Some(42) a method: found through Deref
*boxed + 1 42 an operator: needs the *
`boxed + 1` is E0369. Method calls and coercion sites deref for you;
operators and patterns do not.
6. Rc is "single-threaded"; Arc is the one that crosses
thread read Arc<String>: len 4
strong_count after join: 1
The same spawn with an Rc is E0277: Rc<String> cannot be sent
between threads safely.
Refused by the compiler¶
Claim 4 — an Rc is not a raw pointer.
use std::rc::Rc;
fn main() {
let counted = Rc::new(5u8);
let raw: *const u8 = counted;
println!("{}", raw.is_null());
}
error[E0308]: mismatched types
--> rc_as_a_raw_pointer.rs:5:26
|
5 | let raw: *const u8 = counted;
| --------- ^^^^^^^ expected `*const u8`, found `Rc<u8>`
| |
| expected due to this
|
= note: expected raw pointer `*const u8`
found struct `Rc<u8>`
A &u8 would have coerced to *const u8 on that line; an Rc<u8> does not. Section 4 shows the other differences: an Rc needs dropping and a raw pointer does not, and Option<Rc<u8>> is 8 bytes where Option<*const u8> is 16, because Rc holds a NonNull (alloc/src/rc.rs in the 1.98.0 source).
Claim 5 — operators do not deref for you.
error[E0369]: cannot add `{integer}` to `Box<i32>`
--> box_plus_one.rs:3:26
|
3 | println!("{}", boxed + 1);
| ----- ^ - {integer}
| |
| Box<i32>
|
note: `Box<i32>` does not implement `Add<{integer}>`
And neither do patterns:
fn main() {
let boxed = Box::new(41i32);
match boxed {
41 => println!("forty-one"),
_ => println!("something else"),
}
}
error[E0308]: mismatched types
--> box_in_a_pattern.rs:4:9
|
3 | match boxed {
| ----- this expression has type `Box<i32>`
4 | 41 => println!("forty-one"),
| ^^ expected `Box<i32>`, found integer
|
= note: expected struct `Box<i32>`
found type `{integer}`
help: consider dereferencing to access the inner value using the `Deref` trait
|
3 | match *boxed {
| +
The help line is the fix, and names the trait. Weak's missing Deref is E0614, shown on What makes a pointer smart.
Claim 6 — Rc cannot cross a thread.
use std::rc::Rc;
use std::thread;
fn main() {
let shared = Rc::new(String::from("sent"));
let mine = Rc::clone(&shared);
let handle = thread::spawn(move || mine.len());
println!("{}", handle.join().unwrap());
}
error[E0277]: `Rc<String>` cannot be sent between threads safely
--> rc_across_a_thread.rs:7:32
|
7 | let handle = thread::spawn(move || mine.len());
| ------------- -------^^^^^^^^^^^
| | |
| | `Rc<String>` cannot be sent between threads safely
| | within this `{closure@rc_across_a_thread.rs:7:32: 7:39}`
| required by a bound introduced by this call
|
= help: within `{closure@rc_across_a_thread.rs:7:32: 7:39}`, the trait `Send` is not implemented for `Rc<String>`
Rc's counts are plain Cell<usize>, and two threads updating one without synchronization would lose counts. Section 6 does the same spawn with Arc, whose counts are atomic, and the strong count is back to 1 after the join. Send and Sync is the page for the traits.
From Rust in Action §6.2.3¶
The book's "Smart pointer building blocks" names the types Box, Rc and Vec are built from. Against the 1.98.0 source (rust-src):
| The book names | In 1.98.0 |
|---|---|
core::ptr::Unique, the basis of String, Box<T> and Vec<T> |
exists, and Box<T> is still (Unique<T>, A); it is #[doc(hidden)] and unstable under ptr_internals, and its attribute says to use NonNull instead |
core::ptr::Shared, the basis of Rc<T> and Arc<T> |
does not exist; Rc<T> holds a NonNull<RcInner<T>> |
std::rc::Weak and std::arc::Weak |
std::rc::Weak, and std::sync::Weak — there is no std::arc module |
alloc::raw_vec::RawVec, under Vec<T> and VecDeque<T> |
exists as pub(crate), so it cannot be named outside alloc |
The stable building block for your own smart pointer is NonNull<T> ↗, which Raw pointers introduces. The pointer half of the same chapter is checked in Rust in Action, chapter 6: pointer claims, run.
Where this leaves you¶
Three tests work on anything else you read about smart pointers:
- Does it say where the work happens? A count, a flag or an allocation is run-time work. "Zero-cost" is true of the handle's size and of
Box's one allocation compared withmalloc— not ofRccompared with&T. - Does it say "owns"? Check the type:
Cow::Borrowed,Ref,MutexGuardandWeakdo not own what they point at. - Does it call a smart pointer a raw pointer, or a reference? All three are pointers; only one of them is
unsafeto read.
See also¶
- What makes a pointer smart and What a smart pointer costs — the two lessons these claims are checked against
- What
Cowexplanations get wrong, run — the same kind of page for the clone-on-write smart pointer Rc: the clone that copies a pointer — the count, and the one leak safe Rust permits- Raw pointers — what
Rc::as_ptrgives you
Sources¶
std's Deref ↗ (the "often called smart pointers" sentence), Rc ↗ and std::rc ↗ ("Single-threaded reference-counting pointers"); The Book, ch. 15 ↗; Rust in Action, Tim McNamara, Manning 2021, §6.2.3. Video: Rc and RefCell Smart Pointers - Rust ↗ (danlogs) walks through Rc<RefCell<T>>, the shape Interior mutability outlines.
Po polsku¶
Opisy inteligentnych wskaźników zwykle dobrze oddają ideę i mylą się w jednym słowie. Sprawdzone na rustc 1.98.0:
- „kompilator je usuwa, więc są darmowe” — nie: uchwyt ma rozmiar surowego wskaźnika, ale liczniki
Rc, flagaRefCelli alokacje działają w czasie wykonania; - „
StringiVec<T>to inteligentne wskaźniki” — tak, w sensie dokumentacji std (DerefiDrop); bufor trzymają przez surowy wskaźnik, nie przez referencję; - „inteligentny wskaźnik posiada dane” — zwykle, ale
Cow::BorrowediWeaknie; - „surowe wskaźniki, takie jak
Rc<T>” — nie:Rcto inteligentny wskaźnik (E0308przy próbie przypisania do*const u8), a surowy daje dopieroRc::as_ptr; - „można go używać zamiast wartości bez kłopotu” — dla metod i koercji tak; operatory (
E0369) i wzorce wmatch(E0308) wymagają*; - „
Rcjest jednowątkowy” — tak, i kompilator to egzekwuje (E0277); między wątki idzieArc.
Szukaj po polsku: inteligentne wskaźniki mity · Rc a Arc wątki · rust Rc is not Send · rust smart pointer zero cost