Ownership

#184 Jun 2026

184. Option::take — The Ergonomic Sibling of mem::take, Just for Option

You’ve got an Option<T> behind &mut self and you need the T out. mem::take works, but the field is already an Option.take() reads better and does the same job.

This morning’s bite on std::mem::take covered the general move: swap in T::default(), hand back the original. For Option<T>, the default is None — and the standard library exposes that exact operation as Option::take, so the call site stops looking like memory plumbing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct Connection {
    handle: Option<Socket>,
}

impl Connection {
    fn disconnect(&mut self) -> Option<Socket> {
        // std::mem::take(&mut self.handle)  // works, reads like low-level glue
        self.handle.take()                  // same thing, reads like English
    }
}
# struct Socket;

Internally Option::take is one line: mem::replace(self, None). The win is purely about the call site.

The pattern earns its keep in Drop, where you need to consume an owned resource by value but only have &mut self:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct Worker {
    join: Option<std::thread::JoinHandle<()>>,
}

impl Drop for Worker {
    fn drop(&mut self) {
        if let Some(handle) = self.join.take() {
            let _ = handle.join();   // join() consumes the handle by value
        }
    }
}

Without .take(), you can’t move self.join out (you only have &mut self), and JoinHandle::join takes self by value — so you’re stuck. take() swaps None in and gives you the owned JoinHandle to consume.

It also kills the classic “transfer once” pattern in builders and state machines:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct Pending {
    payload: Option<String>,
}

impl Pending {
    fn send(&mut self) -> Option<String> {
        // Hand the payload to the caller; we no longer own it.
        // Subsequent calls return None.
        self.payload.take()
    }
}

Reach for Option::take whenever the field is already Option<T>. Reach for std::mem::take when the field is some other T: Default and you want the empty version left behind.

#183 Jun 2026

183. std::mem::take — Move Out of &mut self Without the Clone

You need the Vec out of &mut self, the borrow checker says no, so you .clone() it. Don’t. mem::take swaps in the default and hands you the original — zero allocation.

The borrow checker won’t let you move a field out of &mut self, because that would leave self half-initialized. The usual workarounds are ugly: clone the whole thing, or refactor the API to take self by value.

std::mem::take does the right thing in one line. It replaces the field with T::default() and returns the old value. For collections, Default is empty — so there’s no allocation, just a pointer swap.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::mem;

struct Buffer {
    items: Vec<String>,
}

impl Buffer {
    fn drain_all(&mut self) -> Vec<String> {
        // self.items                 // ❌ cannot move out of borrowed content
        // self.items.clone()         // ❌ allocates + copies every String
        mem::take(&mut self.items)    // ✅ swaps in empty Vec, returns the real one
    }
}

Where it really earns its keep is state-machine transitions, where you need to consume the data inside the current variant before swapping the variant:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::mem;

enum Stream {
    Buffering(Vec<u8>),
    Done,
}

impl Stream {
    fn finish(&mut self) -> Vec<u8> {
        if let Stream::Buffering(buf) = self {
            let bytes = mem::take(buf);   // pull the Vec out, leave [] behind
            *self = Stream::Done;          // now safe to overwrite self
            return bytes;
        }
        Vec::new()
    }
}

Without mem::take, that pattern usually devolves into a mem::replace(self, Stream::Done) and a match on the returned value. mem::take is shorter and reads top-to-bottom.

It works for any T: DefaultString, HashMap, Option, Box<[T]>, your own structs that derive Default. If Default isn’t free for your type, reach for mem::replace and pass the sentinel you actually want.

159. Rc<T> — Single-Threaded Shared Ownership

The borrow checker’s one-owner rule is a feature until you’re modeling a graph, a cache, or any structure where “who owns this?” honestly has more than one answer. Rc<T> is the escape hatch: a reference-counted pointer that lets multiple owners share the same heap allocation, single-threaded, no locking, no overhead beyond a counter increment.

The pain: a value with no single owner

Imagine a config blob a couple of subsystems need to read. You don’t want to copy it — it’s big — and you can’t give either subsystem a &Config without inventing a parent that outlives them both:

1
2
3
4
5
6
7
8
struct Config { name: String }

fn build() -> (Logger, Metrics) {
    let cfg = Config { name: "app".into() };
    let logger = Logger { cfg: &cfg };  // error: `cfg` does not live long enough
    let metrics = Metrics { cfg: &cfg };
    (logger, metrics)
}

You could clone() the Config and hand each subsystem its own copy. You could thread the lifetime through every type that touches it. Or you could acknowledge that this value genuinely has multiple owners and say so.

Rc<T>: many owners, one allocation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::rc::Rc;

struct Config { name: String }
struct Logger { cfg: Rc<Config> }
struct Metrics { cfg: Rc<Config> }

let cfg = Rc::new(Config { name: "app".into() });
let logger = Logger { cfg: Rc::clone(&cfg) };
let metrics = Metrics { cfg: Rc::clone(&cfg) };

assert_eq!(logger.cfg.name, "app");
assert_eq!(metrics.cfg.name, "app");

Rc::new puts the Config on the heap alongside a strong-count of 1. Rc::clone doesn’t clone the Config — it bumps the counter and hands back another pointer to the same allocation. When a clone is dropped the count decrements; when the last clone is dropped, the Config is destroyed and the heap memory is freed.

The convention is Rc::clone(&x) rather than x.clone(). Both compile, both do the same thing, but the explicit form makes “this is a cheap counter bump, not a deep copy” obvious at the call site — especially helpful when T itself is Clone.

Inspecting the count

Rc::strong_count is a debugging window into the same counter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use std::rc::Rc;

let a = Rc::new(String::from("shared"));
assert_eq!(Rc::strong_count(&a), 1);

let b = Rc::clone(&a);
let c = Rc::clone(&a);
assert_eq!(Rc::strong_count(&a), 3);

drop(b);
assert_eq!(Rc::strong_count(&a), 2);

You almost never need to read it in production code — its main use is asserting “yes, this is actually shared” in tests, or understanding a memory leak.

You only get &T through an Rc<T>

The price for shared ownership is read-only access. Rc<T> is Deref<Target = T>, but not DerefMut. Try to mutate and the compiler stops you:

1
2
3
4
5
6
use std::rc::Rc;

let cfg = Rc::new(String::from("app"));
let other = Rc::clone(&cfg);
cfg.push_str("-prod");
// error: cannot borrow data in an `Rc` as mutable

This is the correct default. If two owners could both call &mut simultaneously, you’d have a data race in single-threaded code — exactly the bug Rust exists to prevent. So you need a runtime borrow check, which is what RefCell<T> provides.

The classic pattern: Rc<RefCell<T>>

When the shared value also needs to mutate, wrap it in a RefCell. The outer Rc gives you shared ownership; the inner RefCell gives you borrow_mut through &self:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::rc::Rc;
use std::cell::RefCell;

let log: Rc<RefCell<Vec<String>>> = Rc::new(RefCell::new(Vec::new()));

let writer_a = Rc::clone(&log);
let writer_b = Rc::clone(&log);

writer_a.borrow_mut().push("a says hi".into());
writer_b.borrow_mut().push("b says hi".into());

let snapshot = log.borrow();
assert_eq!(snapshot.len(), 2);
assert_eq!(snapshot[0], "a says hi");

Every Rc::clone owns the cell; every borrow goes through RefCell’s runtime check. Forget the inner RefCell and you’ll be staring at cannot borrow as mutable errors. Forget the outer Rc and you can’t share the cell in the first place. The pair shows up so often that “Rc<RefCell<…>>” should land in your fingers as one token.

Rc::clone vs (*rc).clone()

It’s worth seeing the difference once:

1
2
3
4
5
6
7
8
9
use std::rc::Rc;

let a = Rc::new(vec![1, 2, 3]);

let cheap = Rc::clone(&a);        // counter += 1, no allocation
let expensive: Vec<i32> = (*a).clone();   // brand new Vec, separate heap allocation

assert_eq!(Rc::strong_count(&a), 2);   // a + cheap; `expensive` isn't an Rc at all
assert_eq!(expensive, vec![1, 2, 3]);

Rc<T> doesn’t change what T::clone does — it just adds a much cheaper way to make another pointer to the same T. Use Rc::clone for the pointer; reach for (*rc).clone() only when you really do want a fresh, independent T.

The catch: Rc<T> is not Send

Rc is deliberately not thread-safe. The counter increment isn’t atomic; if two threads bumped it simultaneously you could undercount and free a still-referenced allocation. Try to move one across a thread boundary and the compiler refuses:

1
2
3
4
5
6
use std::rc::Rc;
use std::thread;

let a = Rc::new(42);
thread::spawn(move || println!("{a}"));
// error: `Rc<i32>` cannot be sent between threads safely

That refusal is the whole reason Rc is cheap — no atomic ops, no fences. Cross threads and you want the atomically-counted sibling: Arc<T>, which this afternoon’s bite is about. Same API, same shape, just Send + Sync and a slightly costlier clone.

The other catch: reference cycles

Two Rcs pointing at each other never drop, because each is keeping the other’s count above zero:

1
2
3
4
struct Node {
    next: RefCell<Option<Rc<Node>>>,
}
// build a -> b -> a … and neither will ever be freed

Rc is a counter, not a tracing GC — it can’t notice that an island of nodes is unreachable from the outside as long as the nodes are still pointing at each other. The fix is Weak<T>, the non-owning sibling pointer you get from Rc::downgrade. That’s tomorrow’s bite. For now: if your data is a tree, plain Rc<RefCell<T>> is fine; if it’s a graph or has back-pointers, plan for Weak.

#127 May 2026

127. std::mem::swap — Trade Two Values Through Their &mut

The textbook let tmp = a; a = b; b = tmp; falls over the moment a and b are &mut T — you can’t move out of a reference. mem::swap is the generic, safe, two-line answer.

Why the temp-variable dance breaks

If a and b are owned locals, you can shuffle through a temporary just fine. As soon as they’re behind &mut, the borrow checker stops you:

1
2
3
4
5
fn naive<T>(a: &mut T, b: &mut T) {
    // let tmp = *a;   // E0507: cannot move out of `*a`
    // *a = *b;        // (also moves out of *b)
    // *b = tmp;
}

You’d have to require T: Copy (loses generality), T: Clone (extra work), or reach for unsafe { ptr::swap(...) }. None of those is the right answer.

mem::swap is the primitive

std::mem::swap(&mut a, &mut b) swaps the bits behind two mutable references. No traits required, no allocation, no unsafe:

1
2
3
4
5
6
7
8
9
use std::mem;

let mut a = String::from("left");
let mut b = String::from("right");

mem::swap(&mut a, &mut b);

assert_eq!(a, "right");
assert_eq!(b, "left");

Works for any T. The two memory locations exchange contents in-place — one memcpy-sized swap, no clones.

A real shape: front/back double buffering

The pattern that earns mem::swap its keep — flip a “current” and “next” buffer at the end of each frame, reuse both allocations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::mem;

struct DoubleBuffer<T> {
    front: Vec<T>,
    back: Vec<T>,
}

impl<T> DoubleBuffer<T> {
    fn flip(&mut self) {
        mem::swap(&mut self.front, &mut self.back);
    }
}

let mut buf = DoubleBuffer {
    front: vec![1, 2, 3],
    back:  vec![9, 9, 9],
};

buf.flip();

assert_eq!(buf.front, vec![9, 9, 9]);
assert_eq!(buf.back,  vec![1, 2, 3]);

Two fields of the same struct, both behind &mut self — exactly the case the temp-variable dance can’t reach. mem::swap doesn’t care that the references come from the same parent borrow.

Why not slice::swap or Vec::swap?

v.swap(i, j) is the right tool when both values live in the same slice — it does the index trick under the hood so the borrow checker stays happy. mem::swap is the broader primitive: any two &mut T, regardless of whether they share a container. They’re the same idea at different scopes.

When to reach for it

Whenever you need to exchange two owned values through &mut: flipping buffers, rotating state in a tree node, splicing nodes in a linked list, swapping fields during a state transition. mem::take is swap with T::default() on one side; mem::replace is swap with src on one side and the old value returned. Same family — pick the one whose shape matches what you actually want back.

#106 Apr 2026

106. Iterator::by_ref — Take Part of an Iterator and Keep the Rest

Called iter.take(n).collect() and watched the borrow checker swallow the whole iterator? by_ref lets you consume a chunk and keep iterating from where you stopped.

The trap: adapters consume their iterator

Most iterator adapters take self, not &mut self. The moment you write iter.take(2), you have moved iter into the new Take adapter. The original binding is gone:

1
2
3
4
5
6
let v = vec![1, 2, 3, 4, 5];
let mut iter = v.into_iter();

let _first_two: Vec<i32> = iter.take(2).collect();
// let rest: Vec<i32> = iter.collect();
// ^^ error: use of moved value: `iter`

So even though there are clearly elements left, the variable that points at them is no longer usable.

The fix: iter.by_ref()

Iterator::by_ref returns a &mut Self — and &mut I itself implements Iterator whenever I: Iterator. Adapters chained off by_ref() consume from the underlying iterator without moving it:

1
2
3
4
5
6
7
8
let v = vec![1, 2, 3, 4, 5];
let mut iter = v.into_iter();

let first_two: Vec<i32> = iter.by_ref().take(2).collect();
let rest: Vec<i32> = iter.collect();

assert_eq!(first_two, [1, 2]);
assert_eq!(rest, [3, 4, 5]);

Same iterator, two phases, no clones, no indexing.

A real use: parse a header, then a body

The pattern shines when the front of a stream uses different rules than the rest. Pull lines until you hit a blank one, then hand the same iterator to the body parser:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let input = "Subject: hi\nFrom: a@b\n\nbody line 1\nbody line 2";
let mut lines = input.lines();

let headers: Vec<&str> = lines
    .by_ref()
    .take_while(|l| !l.is_empty())
    .collect();

let body: Vec<&str> = lines.collect();

assert_eq!(headers, ["Subject: hi", "From: a@b"]);
assert_eq!(body, ["body line 1", "body line 2"]);

Without by_ref, take_while would have consumed lines whole and the body would be unreachable.

Gotcha: take_while peeks one past

take_while reads the first non-matching element to know when to stop — and that element is gone from the underlying iterator. With by_ref you’ll see this directly:

1
2
3
4
5
6
7
let mut nums = (1..).into_iter();

let small: Vec<i32> = nums.by_ref().take_while(|&n| n < 3).collect();
let next = nums.next();

assert_eq!(small, [1, 2]);
assert_eq!(next, Some(4)); // 3 was eaten by take_while

If you need to keep the boundary element, reach for Peekable instead.

When to use it

Any time you want to apply an adapter to part of an iterator and continue afterwards: chunked parsing, header/body splits, “take the first N then process the rest”, consuming until a sentinel. by_ref() turns “I moved my iterator and now I can’t use it” into a single extra method call.

99. std::mem::replace — Swap a Value and Keep the Old One

mem::take is great until your type doesn’t have a sensible Default. That’s where mem::replace steps in — you pick what gets left behind, and you still get the old value out of a &mut.

The shape of the problem

You can’t move a value out of a &mut T. The borrow checker rightly refuses. mem::take fixes this by swapping in T::default(), but an enum with no obvious default, or a type that deliberately doesn’t implement Default, leaves you stuck.

mem::replace(dest, src) is the escape hatch: it writes src into *dest and hands you back the old value.

1
2
3
4
5
6
7
use std::mem;

let mut greeting = String::from("Hello");
let old = mem::replace(&mut greeting, String::from("Howdy"));

assert_eq!(old, "Hello");
assert_eq!(greeting, "Howdy");

No clones, no unsafe, no Default required.

State machines without a default variant

This is where replace earns its keep. Picture a connection type where none of the variants makes a natural default — Disconnected is fine here, but it might be Error(e) somewhere else, and #[derive(Default)] would be a lie:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
use std::mem;

enum Connection {
    Disconnected,
    Connecting(u32),
    Connected { session: String },
}

fn finalize(conn: &mut Connection) -> Option<String> {
    match mem::replace(conn, Connection::Disconnected) {
        Connection::Connected { session } => Some(session),
        _ => None,
    }
}

let mut c = Connection::Connected { session: String::from("abc123") };
let session = finalize(&mut c);

assert_eq!(session.as_deref(), Some("abc123"));
assert!(matches!(c, Connection::Disconnected));

You get the owned String out of the Connected variant — no cloning the session, no Option<Connection> gymnastics, no unsafe.

Flushing a buffer with a fresh one

mem::take would leave behind an empty Vec with zero capacity. mem::replace lets you pre-size the replacement, which matters if you’re about to refill it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use std::mem;

struct Batch {
    items: Vec<u32>,
}

impl Batch {
    fn flush(&mut self) -> Vec<u32> {
        mem::replace(&mut self.items, Vec::with_capacity(16))
    }
}

let mut b = Batch { items: vec![1, 2, 3] };
let drained = b.flush();

assert_eq!(drained, vec![1, 2, 3]);
assert!(b.items.is_empty());
assert_eq!(b.items.capacity(), 16);

Same trick works for swapping in a String::with_capacity(...), a pre-allocated HashMap, or anything where the replacement’s shape is tuned for what comes next.

When to reach for which

mem::take when the type has a cheap, meaningful Default and you don’t care about the leftover. mem::replace when you need to control the replacement — an enum variant, a pre-sized collection, a sentinel value. Both are safe, both are O(1), and both read more clearly than the Option::take / unwrap dance.

#033 Mar 2026

33. std::mem::take

Ever tried to move a value out of a &mut reference? The borrow checker won’t let you — but std::mem::take will. It swaps the value out and leaves Default::default() in its place.

1
2
3
4
5
6
7
use std::mem;

let mut name = String::from("Ferris");
let taken = mem::take(&mut name);

assert_eq!(taken, "Ferris");
assert_eq!(name, ""); // left with String::default()

This is especially useful when working with enum state machines where you need to consume the current state:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::mem;

enum State {
    Running(String),
    Stopped,
}

impl Default for State {
    fn default() -> Self { State::Stopped }
}

fn reset(state: &mut State) -> Option<String> {
    match mem::take(state) {
        State::Running(data) => Some(data),
        State::Stopped => None,
    }
}

Without mem::take, you’d need .clone() or unsafe gymnastics to get the value out. See also mem::replace for when you want to specify what to leave behind instead of using Default.