#246 Jul 2026

246. Iterator::peekable — Look at the Next Item Without Consuming It

Sometimes you need to see the next element to decide what to do — but calling .next() eats it. .peekable() gives you a .peek() that shows the next item while leaving it in place.

The problem: deciding based on what comes next

A classic case is joining items with a separator. You want a comma between elements but not a trailing one, so you need to know “is there another item after this?” A plain iterator can’t tell you without consuming it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn join_csv(items: &[&str]) -> String {
    let mut out = String::new();
    let mut it = items.iter().peekable();
    while let Some(item) = it.next() {
        out.push_str(item);
        if it.peek().is_some() {
            out.push_str(", ");
        }
    }
    out
}

assert_eq!(join_csv(&["a", "b", "c"]), "a, b, c");
assert_eq!(join_csv(&["solo"]), "solo");

peek() returns Option<&Item> — a reference to the next value if there is one — without advancing the iterator. The next .next() still hands you that same element.

The real power: peek to decide, then consume

Peeking shines when you’re parsing a stream and want to grab a run of elements that match a condition. Look at the front, and only call .next() once you’ve decided to keep it:

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

// Read a run of digits off the front of a char stream.
fn take_number(chars: &mut Peekable<Chars>) -> u32 {
    let mut n = 0;
    while let Some(&c) = chars.peek() {
        match c.to_digit(10) {
            Some(d) => {
                n = n * 10 + d;
                chars.next(); // commit: actually consume it
            }
            None => break, // leave the non-digit in place
        }
    }
    n
}

let mut chars = "42px".chars().peekable();
assert_eq!(take_number(&mut chars), 42);
// The "px" is untouched, ready for the next parser.
assert_eq!(chars.collect::<String>(), "px");

The non-digit p stays in the iterator because we peeked at it instead of consuming it — the caller picks up exactly where the number ended.

next_if for the common case

When the pattern is “consume the next item only if it matches,” next_if does the peek-and-maybe-advance in one call:

1
2
3
4
let mut it = [1, 2, 3].iter().peekable();
assert_eq!(it.next_if(|&&x| x == 1), Some(&1)); // matches, consumed
assert_eq!(it.next_if(|&&x| x == 99), None);    // no match, 2 stays put
assert_eq!(it.next(), Some(&2));

There’s also next_if_eq for the “advance past this exact value” case. Whenever you find yourself wishing you could un-call .next(), reach for .peekable().

#245 Jul 2026

245. rem_euclid — The Modulo That Never Goes Negative

-1 % 5 is -1 in Rust, not 4% keeps the sign of the dividend. When you want a true wrap-around index or clock value, (-1i32).rem_euclid(5) gives you the 4 you actually meant.

Why % bites you

Rust’s % is a remainder, not a mathematical modulo. Its result takes the sign of the left operand:

1
2
assert_eq!(-1 % 5, -1);
assert_eq!(-8 % 3, -2);

That’s fine for the positive case, but the moment a negative number sneaks in — a decrementing index, an offset that can go below zero — you get a negative result. Feed that into arr[idx as usize] and you either panic or cast into a giant number.

rem_euclid always lands in [0, n)

1
2
3
assert_eq!((-1i32).rem_euclid(5), 4);
assert_eq!((-8i32).rem_euclid(3), 1);
assert_eq!(7i32.rem_euclid(5), 2);

For a positive divisor, rem_euclid is guaranteed to return a non-negative result strictly less than the divisor — exactly what “wrap around” should mean.

Where it pays off: wrapping an index

Stepping backwards through a ring buffer is the textbook case. With % you’d have to add the length back in by hand to avoid going negative:

1
2
3
4
5
6
7
8
9
let buf = ['a', 'b', 'c', 'd'];
let len = buf.len() as i32;

// Move one step left from index 0, wrapping to the end
let mut idx = 0i32;
idx = (idx - 1).rem_euclid(len);

assert_eq!(idx, 3);
assert_eq!(buf[idx as usize], 'd');

No + len fixup, no branch on “did it go negative.” The same trick handles clock arithmetic ((hour + delta).rem_euclid(24)) and angle normalization.

div_euclid is its partner: (-8).div_euclid(3) == -3, and the identity a == b * a.div_euclid(b) + a.rem_euclid(b) always holds. Whenever you catch yourself writing ((x % n) + n) % n to force a positive remainder, that’s rem_euclid spelled the hard way.

#244 Jul 2026

244. clone_from — Reuse the Buffer You Already Have Instead of Reallocating

*dst = src.clone() throws away dst’s heap buffer and allocates a brand-new one every time. dst.clone_from(&src) copies into the storage dst already owns — reusing its capacity instead of freeing and re-grabbing it.

The hidden allocation in assignment

Cloning into an existing variable looks free, but it isn’t:

1
2
3
4
5
6
7
8
9
let mut dst = String::with_capacity(64);
dst.push_str("previous value");

let src = String::from("new value");

// Drops dst's 64-byte buffer, allocates a fresh one for src's length
dst = src.clone();

assert_eq!(dst, "new value");

src.clone() builds a completely new String with its own allocation, then the assignment drops the old dst — buffer and all. In a loop that runs thousands of times, that’s a free-then-allocate churn on every iteration, even when the old buffer was plenty big.

clone_from copies into place

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let mut dst = String::with_capacity(64);
dst.push_str("previous value");

let src = String::from("new value");

// Overwrites dst's existing buffer; keeps the capacity if it fits
dst.clone_from(&src);

assert_eq!(dst, "new value");
assert!(dst.capacity() >= 64); // same allocation, reused

Clone::clone_from is a provided trait method whose whole job is “make self equal to source, reusing self’s resources where possible.” For String and Vec, that means copying the bytes into the buffer that’s already there and only reallocating if it’s too small. The default impl just does *self = source.clone(), but the collections override it to reuse storage.

Where it pays off: the reused accumulator

The classic win is a buffer you refresh every pass through a loop — pair it with the reuse-one-buffer pattern:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let inputs = vec![vec![1, 2, 3], vec![4, 5, 6, 7], vec![8]];
let mut scratch: Vec<i32> = Vec::new();

for row in &inputs {
    scratch.clone_from(row); // reuses scratch's capacity each time
    scratch.iter_mut().for_each(|x| *x *= 10);
    // ... use scratch ...
}

assert_eq!(scratch, vec![80]); // last row, scaled

Every clone_from after the first reuses whatever capacity scratch grew to, so the loop stops hammering the allocator. Swap in scratch = row.clone() and you’re back to a fresh allocation on each turn.

It works for any nested owned type too — a Vec<String> clones each element with clone_from, so inner buffers get reused, not rebuilt. Whenever you catch yourself writing dst = src.clone() for a dst you already own, clone_from is the version that doesn’t throw the buffer away.

#243 Jul 2026

243. slice::as_flattened — Treat a Slice of Arrays as One Flat Slice, No Copy

You’ve got a Vec<[f32; 3]> of RGB pixels and an API that wants &[f32]. The manual flatten allocates a whole second buffer. as_flattened hands you the same bytes as a flat &[f32] — zero copies, zero allocation.

The copy you didn’t need

A slice of fixed-size arrays is already contiguous in memory. But reach for a flat view the obvious way and you rebuild it element by element into a fresh Vec:

1
2
3
4
5
6
let pixels: Vec<[f32; 3]> = vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];

// Rebuilds every value into a new allocation
let flat: Vec<f32> = pixels.iter().flatten().copied().collect();

assert_eq!(flat, vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0]);

That collect walks all six values and heap-allocates a second buffer — pure waste when the layout you want is already sitting there.

as_flattened is a free reinterpretation

1
2
3
4
5
6
let pixels: Vec<[f32; 3]> = vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];

let flat: &[f32] = pixels.as_flattened();

assert_eq!(flat, &[1.0, 0.0, 0.0, 0.0, 1.0, 0.0]);
assert_eq!(flat.len(), 6); // outer_len * N

<[[T; N]]>::as_flattened takes &[[T; N]] and returns &[T] covering the exact same memory. No copy, no allocation — just a pointer and a length. The result borrows the original, so it stays as cheap as it looks.

Mutate through the flat view

as_flattened_mut gives you &mut [T], so you can run a flat transform over structured data without unpacking it:

1
2
3
4
5
6
7
let mut rows = [[1, 2, 3], [4, 5, 6]];

for v in rows.as_flattened_mut() {
    *v *= 10;
}

assert_eq!(rows, [[10, 20, 30], [40, 50, 60]]);

Same storage, edited in place — the array grouping is still there when you’re done.

Where it shines: handing structured data to flat APIs

Vertex buffers, audio frames, matrices — anything you model as [T; N] but a lower-level API wants as one long run:

1
2
3
4
5
6
7
8
9
// A 2x3 matrix stored as rows
let matrix = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];

fn dot(a: &[f64], b: &[f64]) -> f64 {
    a.iter().zip(b).map(|(x, y)| x * y).sum()
}

let flat = matrix.as_flattened();
assert_eq!(dot(flat, flat), 91.0); // 1+4+9+16+25+36

You keep the readable [[f64; 3]; 2] shape in your own code and pass a &[f64] across the boundary — no glue buffer in between. Whenever you catch yourself flatten().collect()-ing a slice of arrays just to change its type, as_flattened is the zero-cost version.

#242 Jul 2026

242. slice::binary_search_by_key — Find a Record by One Field, No Hand-Written Comparator

Binary searching a slice of structs by one field? Don’t hand-roll a .cmp() closure and risk flipping the comparison — project the key and let the stdlib do the rest.

You have a slice sorted by some field and want to find an element by that field. The reflex is binary_search_by with a closure that spells out the comparison — and it’s easy to get the argument order backwards, which silently breaks the search:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#[derive(Debug, PartialEq)]
struct Employee {
    id: u32,
    name: &'static str,
}

let staff = [
    Employee { id: 3,  name: "Ada" },
    Employee { id: 7,  name: "Bo"  },
    Employee { id: 12, name: "Cy"  },
];

// The awkward way — you write (and can mis-order) the comparator
let idx = staff.binary_search_by(|e| e.id.cmp(&7));
assert_eq!(idx, Ok(1));

binary_search_by_key takes the target and a closure that projects the key. No .cmp(), nothing to get backwards:

1
2
3
let idx = staff.binary_search_by_key(&7, |e| e.id);
assert_eq!(idx, Ok(1));
assert_eq!(staff[idx.unwrap()].name, "Bo");

On a miss you get Err(i) — the index where the element would go to keep the slice sorted, so you can insert without a second search:

1
2
3
4
match staff.binary_search_by_key(&10, |e| e.id) {
    Ok(i)  => println!("found at {i}"),
    Err(i) => println!("would insert at {i}"), // Err(2)
}

One rule: the slice must already be sorted by the same key you project, otherwise the result is unspecified. When that holds, it’s O(log n) instead of the O(n) scan you’d write by hand.

#241 Jul 2026

241. slice::split_first — Peel the Head Off a Slice, Keep the Tail, No Indexing

slice[0] panics on an empty slice, and &slice[1..] is a second chance to get the bounds wrong. split_first hands you the head and the tail together — or None if there’s nothing there — so the empty case is a pattern, not a panic.

The manual head-and-tail

Reach for the first element and the rest, and you write two indexing operations that both assume the slice is non-empty:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn describe(items: &[&str]) -> String {
    if items.is_empty() {
        return "nothing".to_string();
    }
    let first = items[0];      // panics if you forget the guard above
    let rest = &items[1..];    // and so does this
    format!("{first} plus {} more", rest.len())
}

assert_eq!(describe(&["a", "b", "c"]), "a plus 2 more");

The bounds check is real, but it’s on you to remember the is_empty guard. Drop it and an empty slice panics at runtime.

split_first gives you both, safely

1
2
3
4
5
6
7
8
9
fn describe(items: &[&str]) -> String {
    match items.split_first() {
        Some((first, rest)) => format!("{first} plus {} more", rest.len()),
        None => "nothing".to_string(),
    }
}

assert_eq!(describe(&["a", "b", "c"]), "a plus 2 more");
assert_eq!(describe(&[]), "nothing"); // no panic — the None arm handles it

split_first returns Option<(&T, &[T])>: the first element and a slice of everything after it, or None when the slice is empty. The empty case can’t be forgotten — it’s a variant you have to match.

split_last peels from the other end

1
2
3
4
5
6
let path = ["usr", "local", "bin"];

if let Some((last, parents)) = path.split_last() {
    assert_eq!(*last, "bin");
    assert_eq!(parents, &["usr", "local"]);
}

Same shape, mirrored: the last element plus everything before it.

Where it shines: recursion without index math

Because the tail is just another slice, split_first makes structural recursion clean — the base case is None, and there’s no i + 1 to fumble:

1
2
3
4
5
6
7
8
9
fn sum(slice: &[i32]) -> i32 {
    match slice.split_first() {
        Some((head, tail)) => head + sum(tail),
        None => 0,
    }
}

assert_eq!(sum(&[1, 2, 3, 4]), 10);
assert_eq!(sum(&[]), 0);

Need to mutate as you walk? split_first_mut and split_last_mut return (&mut T, &mut [T]), so you can edit the head and recurse into the tail without a borrow fight. Whenever you catch yourself pairing slice[0] with &slice[1..], this is the method that folds both — and the empty check — into one.

#240 Jul 2026

240. to_le_bytes / from_le_bytes — Serialize Integers Without unsafe or Bit-Shifting

Packing a u32 into four bytes by hand means a stack of >> and as u8 casts — and one wrong shift silently corrupts your data. Every integer type already knows how to lay itself out, in the exact endianness you ask for.

The hand-rolled shift-and-mask

Serializing an integer to bytes the manual way is easy to get subtly wrong:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let n: u32 = 300;

// big-endian by hand: shift, cast, repeat
let bytes = [
    (n >> 24) as u8,
    (n >> 16) as u8,
    (n >> 8) as u8,
    n as u8,
];
assert_eq!(bytes, [0, 0, 1, 44]);

It works, but the shift amounts are magic numbers, the byte order is implicit, and reversing it (parsing bytes back into a u32) means another shift ladder in the opposite direction.

to_*_bytes hands you the array

Every integer type has to_be_bytes, to_le_bytes, and to_ne_bytes, each returning a fixed-size [u8; N]:

1
2
3
4
let n: u32 = 0x12345678;

assert_eq!(n.to_be_bytes(), [0x12, 0x34, 0x56, 0x78]); // big-endian
assert_eq!(n.to_le_bytes(), [0x78, 0x56, 0x34, 0x12]); // little-endian

The endianness is spelled out in the method name, the array length is checked at compile time ([u8; 4] for a u32), and there’s no cast to fumble.

from_*_bytes parses it back

The inverse takes the same fixed-size array and rebuilds the integer:

1
2
3
4
let n: u32 = 300;
let wire = n.to_be_bytes();

assert_eq!(u32::from_be_bytes(wire), 300); // exact round-trip

Reading an integer out of a buffer

The array size is part of the type, so slice a &[u8] and try_into an array before parsing — the length check happens for you:

1
2
3
4
let buf = [0x00, 0x00, 0x01, 0x2c, 0xff]; // 4-byte int + trailing byte

let n = u32::from_be_bytes(buf[..4].try_into().unwrap());
assert_eq!(n, 300);

If the slice is the wrong length, try_into returns an Err instead of reading past the end — no unsafe, no out-of-bounds read.

Use to_be_bytes / from_be_bytes for network and file formats (big-endian is the usual “wire” order), _le_ when a spec demands little-endian, and reach for _ne_ (native) only for in-memory blobs that never leave the machine. Floats have the same methods, and to_bits / from_bits if you want the raw u32/u64 first.

#239 Jul 2026

239. Vec::drain — Remove a Range and Keep What You Pulled Out

truncate throws elements away. split_off allocates a second Vec. When you want to remove a range from a Vec and actually use those elements — and keep the rest — drain hands them to you as an iterator and shifts everything else down for you.

The manual remove-and-collect

Say you want to pull a batch out of the front of a queue and process it:

1
2
3
4
5
6
7
8
9
let mut queue = vec![1, 2, 3, 4, 5];

// take the first three, keep the rest
let mut batch = Vec::new();
for _ in 0..3 {
    batch.push(queue.remove(0)); // each remove is O(n): shifts everything left
}
assert_eq!(batch, vec![1, 2, 3]);
assert_eq!(queue, vec![4, 5]);

Every remove(0) shifts the whole tail down one slot — quadratic for a batch, and the intent is buried in a loop.

drain does it in one pass

1
2
3
4
5
6
let mut queue = vec![1, 2, 3, 4, 5];

let batch: Vec<_> = queue.drain(0..3).collect();

assert_eq!(batch, vec![1, 2, 3]);
assert_eq!(queue, vec![4, 5]);

drain(range) removes that range, yields the removed elements in order, and shifts the remaining tail down once. You own the drained values — collect them, iterate them, or pipe them straight into another call.

Drain the whole thing to reuse the allocation

drain(..) empties the Vec but keeps its capacity, so the buffer is ready to refill without reallocating:

1
2
3
4
5
6
7
8
let mut buf = vec![10, 20, 30];
let cap = buf.capacity();

let sum: i32 = buf.drain(..).sum(); // consume every element by value
assert_eq!(sum, 60);

assert!(buf.is_empty());
assert_eq!(buf.capacity(), cap); // allocation retained — refill it next loop

That’s the move-out-and-reuse trick: unlike into_iter(), which consumes the Vec, drain(..) leaves you an empty-but-allocated Vec to keep using.

The removal happens even if you don’t consume it

Drain is a draining iterator: dropping it removes the range regardless of how many items you pulled. So queue.drain(1..4); on its own still deletes that range — you don’t have to .collect() to make it take effect.

1
2
3
let mut v = vec!['a', 'b', 'c', 'd', 'e'];
v.drain(1..4); // dropped immediately; range is gone
assert_eq!(v, vec!['a', 'e']);

Reach for drain over retain when you’re removing a contiguous range by position (not a predicate), and over split_off when you don’t want a second allocation. If you need conditional removal from anywhere, that’s extract_if; if you just want the values gone, truncate is cheaper.

#238 Jul 2026

238. slice::chunks_exact_mut — Edit a Slice in Fixed-Size Blocks Without Index Math

Processing a buffer N elements at a time usually means a while i + N <= len loop and a pile of buf[i..i+N] slicing. chunks_exact_mut hands you each fixed-size block as a &mut [T] — no index bookkeeping, no off-by-one.

The manual-window loop

1
2
3
4
5
6
7
let mut buf = [10, 20, 30, 40, 50, 60];
let mut i = 0;
while i + 2 <= buf.len() {
    buf[i..i + 2].reverse(); // swap each adjacent pair
    i += 2;
}
assert_eq!(buf, [20, 10, 40, 30, 60, 50]);

It works, but every one of i + 2, <= len, and i += 2 is a place to get the bounds wrong.

chunks_exact_mut yields the blocks for you

1
2
3
4
5
let mut buf = [10, 20, 30, 40, 50, 60];
for pair in buf.chunks_exact_mut(2) {
    pair.reverse();
}
assert_eq!(buf, [20, 10, 40, 30, 60, 50]);

Each pair is a &mut [T; 2]-shaped slice you can mutate in place. The iterator stops once fewer than 2 elements remain, so you never index past the end.

The “exact” part: a leftover tail is skipped, not sliced

Unlike chunks_mut, the last partial block is not yielded — that guarantee is exactly why the block size is reliable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let mut buf = [1, 2, 3, 4, 5]; // 5 isn't a multiple of 2
let mut it = buf.chunks_exact_mut(2);
for pair in it.by_ref() {
    pair.swap(0, 1);
}
// grab the odd element that didn't fill a block
let tail = it.into_remainder();
assert_eq!(tail, &mut [5][..]);

assert_eq!(buf, [2, 1, 4, 3, 5]); // last 5 left untouched

Reach for the remainder through by_ref() + into_remainder(): iterate the full blocks, then claim whatever fell short. If you drop the loop’s by_ref(), the iterator is moved and into_remainder is unavailable.

Why not just chunks_mut?

chunks_mut(n) also walks a slice in steps of n, but its final chunk can be shorter than n, so any code assuming a fixed width needs a length check every iteration. chunks_exact_mut trades that partial tail for a compile-time-friendly promise that every yielded block is exactly n long — which also lets the optimizer generate tighter code. There’s a read-only chunks_exact for &[T], and as_chunks_mut if you want real &mut [T; N] arrays instead of slices.

#237 Jul 2026

237. Iterator::eq — Compare Two Sequences Without Collecting Them First

Comparing a Vec to an array, or a filtered iterator to an expected result? You don’t need to collect() both sides into the same type first.

The usual reflex is to force both sequences into matching containers so == works:

1
2
3
4
5
let v = vec![1, 2, 3];
let a = [1, 2, 3];

// Allocates a throwaway Vec just to compare.
assert_eq!(v, a.to_vec());

Iterator::eq compares element-by-element straight off the iterators. No allocation, and the two sides don’t even have to be the same container type:

1
2
3
4
let v = vec![1, 2, 3];
let a = [1, 2, 3];

assert!(v.iter().eq(a.iter()));

It short-circuits, so a length or value mismatch stops early instead of walking both sequences to the end:

1
assert!([1, 2, 3].iter().ne([1, 2].iter()));

The real payoff is comparing a lazy chain to an expected sequence without materializing it:

1
2
let evens = (1..=6).filter(|n| n % 2 == 0);
assert!(evens.eq([2, 4, 6]));

And the same family does lexicographic ordering, again with no intermediate Vec:

1
2
3
4
use std::cmp::Ordering;

assert_eq!([1, 2, 3].iter().cmp([1, 2, 4].iter()), Ordering::Less);
assert!([1, 2].iter().lt([1, 3].iter()));

So the toolkit is eq, ne, cmp, lt, le, gt, ge — full equality and ordering over any two iterators, no collecting required.