A Rust reference
for people writing Rust.
Part dictionary, part wiki — meant to be kept open in a second tab while you code.
- what
?desugars to - whether
Rcis thread-safe - how a
matchguard behaves
You want the answer, a snippet that compiles, and a short note on why — not a chapter.
Every page is distilled from the official Rust documentation — the Book, the Reference, the Nomicon, the API guidelines, std docs — and the mainstream Rust books. It is curated, not invented. If something is wrong, outdated or misleading, that is exactly the kind of feedback worth sending.
What a page looks like
Live excerpt · Concepts → Design Patterns & IdiomsA concept page doesn’t stop at an explanation. Best practices & deeper information breaks the topic into concrete scenarios — “creating a new object”, “working with collections” — each with a recommended way to handle it, the code, and the reasoning.
// <-
3 why this way, not another
Modifying an existing object
A batch processor needs to drain its pending items and hand them to a
worker while leaving self in a valid, empty state — mem::take moves
the Vec out without cloning it and without ever leaving the field
uninitialized.
use std::mem;
struct BatchQueue {
pending: Vec<String>,
}
impl BatchQueue {
fn push(&mut self, item: String) {
self.pending.push(item);
}
fn drain(&mut self) -> Vec<String> {
mem::take(&mut self.pending) // <- moves `pending` out, replaces it with Vec::default() (empty)
}
}
let mut queue = BatchQueue { pending: Vec::new() };
queue.push("order-1".to_string());
queue.push("order-2".to_string());
let batch = queue.drain(); // owns the items; queue.pending is now empty, not uninitialized
println!("{batch:?}");
Why this way: mem::take moves the Vec out in one step instead of
cloning it just to satisfy the "every field must hold a value" rule,
which the
Rust Design Patterns
book documents as the idiomatic way to change or extract a value behind
&mut self without an unnecessary allocation.
Modifying an existing object
A sensor driver transitions from Sampling to Done and needs to carry
the started_at_ms timestamp out of the old state to compute an elapsed
time — mem::replace moves the whole old variant out by value so its
data can be used, with no clone and no Option-wrapped field anywhere in
the struct.
use core::mem;
enum SensorState {
Idle,
Sampling { started_at_ms: u32 },
Done { elapsed_ms: u32, reading: i16 },
}
struct Driver {
state: SensorState,
}
impl Driver {
fn finish_sampling(&mut self, now_ms: u32, reading: i16) {
let old = mem::replace(&mut self.state, SensorState::Idle); // <- moves the old variant out; field never sits empty
self.state = match old {
SensorState::Sampling { started_at_ms } => SensorState::Done {
elapsed_ms: now_ms - started_at_ms,
reading,
},
other => other, // no-op transition if called from the wrong state
};
}
}
let mut driver = Driver { state: SensorState::Sampling { started_at_ms: 1_000 } };
driver.finish_sampling(1_250, 421);
Why this way: mem::replace gets ownership of started_at_ms out of
the old Sampling variant without cloning the enum or wrapping state
in Option<SensorState> just to satisfy the borrow checker — on a
target with no allocator, this is not an optimization over cloning, it's
the only option, since SensorState here holds no Clone impl and
wrapping every read site in Option handling for a state that's always
present would be pure boilerplate; the
Rust Design Patterns
book documents this exact shape as the idiomatic way to change a value
behind &mut self.
Approaches
Community-contributed · live on the site todayThere is rarely one right way to do something in Rust. Every scenario starts with the site’s recommended Classic solution; anyone can add an alternative implementation of the exact same scenario, attributed to them. Switch below — the code, the explanation and the byline all change together.
Interior mutability
Swapping the string held inside a RefCell for a new one, while getting
the old string back to log it, needs to move the old value out of a
borrowed &mut String — exactly what mem::replace is for.
use std::cell::RefCell;
use std::mem;
struct Session {
last_message: RefCell<String>,
}
impl Session {
fn set_message(&self, new_message: String) -> String {
let mut slot = self.last_message.borrow_mut(); // <- RefMut<String>, derefs to &mut String
mem::replace(&mut *slot, new_message) // <- swaps in `new_message`, returns the old one by value
}
}
let session = Session { last_message: RefCell::new("connected".to_string()) };
let previous = session.set_message("processing".to_string());
println!("{previous}"); // "connected"
Why this way: RefCell::borrow_mut only ever gives out a &mut T,
never ownership of the T itself, so mem::replace is the standard way
to pull an owned value out of interior-mutable storage while leaving a
valid replacement behind, per the
std docs for mem::replace.
RefCell already packages this exact swap as a method. replace takes the
mutable borrow and performs the mem::replace in one call, returning the
old value, so the intermediate RefMut binding and the &mut *slot
reborrow both disappear.
use std::cell::RefCell;
struct Session {
last_message: RefCell<String>,
}
impl Session {
fn set_message(&self, new_message: String) -> String {
self.last_message.replace(new_message) // <- borrows and swaps in one step, returns the old String
}
}
let session = Session { last_message: RefCell::new("connected".to_string()) };
let previous = session.set_message("processing".to_string());
println!("{previous}"); // "connected"
Why this way: there is no reborrow to get wrong, and the borrow is held
for exactly the length of the swap rather than for as long as the binding
stays in scope — which is what turns a later borrow() in the same
function into a panic. The limitation is reach: replace exists on
RefCell and Cell, so the moment the value lives behind a plain
&mut — a struct field, a slice element, a MutexGuard — mem::replace
is the tool again. It also panics on an outstanding borrow with no way to
recover; try_borrow_mut plus mem::replace is the version that lets you
handle that case instead of aborting.
Browse
306 pages · 23 groups · press/ to search
Syntax
180 pages · a dictionary, one page per tokenConcepts
126 pages · a wiki with scenarios and approachesBeyond the reference
Code-first write-ups — no think-pieces
Community articles that show real, compiling Rust and explain it: how something works under the hood, or how to build it. Opinion pieces don’t qualify.
Read the articles →The same three sections, every crate
Overview, when to use it, API map — in that order, every time. The fixed shape is the point: the second crate page you read is faster than the first.
Look up a crate →GitHub Discussions, in the site’s own styling
Threads and replies live on GitHub Discussions; the site renders a read-only, near-live mirror, so discussion sits right next to the reference.
Browse the threads →How to help
The aim is for this reference to grow with contributions from people writing Rust, not just the maintainer. Contributions of all kinds are welcome — pointing out wrong information, reporting bugs, flagging what is missing, adding an article, covering a crate, or just taking part in the conversations. Enjoy your coding!
- ApproachThe smallest useful PR. An additive markdown block on a scenario you know a better way through. You never touch anyone else’s content.
- ArticleA technical, code-first piece under
pages/articles/, with your byline on it. - Crate pageOne crate, the three fixed sections, one entry per API item.
- CorrectionHighest priority of all. A reference is only worth trusting if it gets corrected.
- ConversationNo PR needed — post on GitHub Discussions and it appears at the next rebuild.