236. Result::flatten — Collapse a Result<Result<T, E>, E> in One Call

Ended up with a Result<Result<T, E>, E> and reached for a nested match to peel it? When both layers share the same error type, Result::flatten (stable since 1.89) does it in one call.

How you get a doubly-wrapped Result

It usually sneaks in when you map a fallible operation over something that’s already a Result:

1
2
3
4
5
6
7
8
fn first_word(line: Result<&str, String>) -> Result<Result<u32, String>, String> {
    line.map(|s| {
        s.split_whitespace()
            .next()
            .ok_or_else(|| "empty line".to_string())
            .and_then(|w| w.parse::<u32>().map_err(|e| e.to_string()))
    })
}

The outer Result is “did we have a line?”, the inner one is “did it parse?”. Same String error on both — but the type is now Result<Result<u32, String>, String>, which no caller wants to touch.

The manual peel

1
2
3
4
5
6
7
8
let nested: Result<Result<u32, String>, String> = Ok(Ok(42));

let flat: Result<u32, String> = match nested {
    Ok(inner) => inner,   // inner is itself a Result
    Err(e) => Err(e),
};

assert_eq!(flat, Ok(42));

Correct, but it’s boilerplate that hides the intent.

flatten does exactly this

1
2
3
4
5
6
7
let ok:    Result<Result<u32, String>, String> = Ok(Ok(42));
let inner: Result<Result<u32, String>, String> = Ok(Err("bad".into()));
let outer: Result<Result<u32, String>, String> = Err("io".into());

assert_eq!(ok.flatten(),    Ok(42));
assert_eq!(inner.flatten(), Err("bad".to_string()));
assert_eq!(outer.flatten(), Err("io".to_string()));

Ok(Ok(x)) becomes Ok(x); either Err — inner or outer — passes straight through. Option has the same method, Option::flatten, for Option<Option<T>>.

The catch: error types must match

flatten is Result<Result<T, E>, E>Result<T, E> — one E. If the two layers carry different error types, flatten can’t merge them. Reach for and_then instead, which lets ?-style conversion do the work:

1
2
3
4
5
let nested: Result<Result<u32, String>, String> = Ok(Ok(7));

// same as .flatten() here, but and_then can also adapt the inner error
let flat = nested.and_then(|inner| inner);
assert_eq!(flat, Ok(7));

When the errors already line up, flatten is the clearest way to say “unwrap one layer of nesting.”

#235 Jul 2026

235. Iterator::rposition — Find the Last Match Without Reversing-and-Subtracting

Need the index of the last element that matches? The reflex is iter().rev().position(...) then len - 1 - i — and that arithmetic is a classic off-by-one. rposition searches from the back and hands you the real forward index.

The reversed-index trap

1
2
3
4
5
6
7
let bytes = [b'a', b'/', b'b', b'/', b'c'];

// "index of the last slash" — the fragile way
let from_end = bytes.iter().rev().position(|&b| b == b'/').unwrap();
let idx = bytes.len() - 1 - from_end;

assert_eq!(idx, 3);

position on a reversed iterator counts from the end, so you have to flip it back with len - 1 - i. Get the - 1 wrong and you’re off by one.

rposition does the flip for you

1
2
3
4
5
let bytes = [b'a', b'/', b'b', b'/', b'c'];

let idx = bytes.iter().rposition(|&b| b == b'/');

assert_eq!(idx, Some(3)); // real index, counted from the front

rposition walks from the back but returns the index in the original, forward order — no arithmetic, and None when nothing matches instead of a panic on the empty case.

It short-circuits from the right

Just as position stops at the first match from the front, rposition stops at the first match from the back — so it only scans the tail it needs:

1
2
3
4
5
let nums = [1, 2, 3, 4, 5, 6];

// last even number
let idx = nums.iter().rposition(|&n| n % 2 == 0);
assert_eq!(idx, Some(5)); // stopped immediately at 6

The requirement

rposition needs a DoubleEndedIterator (so it can walk backwards) that is also an ExactSizeIterator (so it knows the length to report the front index). Slices, Vec, and arrays give you both. A lazy adapter like filter isn’t ExactSizeIterator, so index a slice or collect first.

For string byte or substring searches, str::rfind is the more direct tool — but for an arbitrary predicate over any exact-size sequence, rposition is the one to reach for.

#234 Jul 2026

234. core::range::Range — A Range That's Copy, So You Can Store and Reuse It

The classic 0..3 isn’t Copy — it is the iterator, so it gets consumed and can’t live in a Copy struct. Rust 1.96 stabilises core::range::Range, which is Copy and iterates through IntoIterator instead.

Why the old Range fights you

std::ops::Range implements Iterator directly. That’s convenient in a for loop, but it means the range is mutable iterator state, so it can’t be Copy:

1
2
3
let r = 0..3;
let total: i32 = r.sum();   // moves r
// let n = r.count();       // error: use of moved value: r

It also can’t sit inside a Copy type, which is annoying when you want a lightweight span:

1
2
#[derive(Clone, Copy)]        // error: the trait Copy is not
struct Span(std::ops::Range<usize>);  // implemented for Range<usize>

The new core::range::Range is Copy

Stabilised in 1.96, core::range::Range implements IntoIterator rather than Iterator, which frees it to be Copy. Convert a literal range into it with .into():

1
2
3
4
5
6
7
use core::range::Range;

let r: Range<usize> = (0..3).into();
let total: usize = r.into_iter().sum();   // r is Copy, not moved
let count = r.into_iter().count();        // still usable
assert_eq!(total, 3);
assert_eq!(count, 3);

Now it fits in a Copy struct

Because it’s Copy, you can store a range as a cheap, copyable span and still index slices with it directly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use core::range::Range;

#[derive(Clone, Copy)]
struct Span(Range<usize>);

impl Span {
    fn of(self, s: &str) -> &str { &s[self.0] }
}

let span = Span((2..5).into());
let a = span;                 // copies, no move
assert_eq!(a.of("hello world"), "llo");
assert_eq!(span.of("hello world"), "llo");   // span still valid

No more splitting a range into separate start / end fields just to keep a struct Copy.

Heads up

0..3 syntax still produces the legacy std::ops::Range for now — the new types come in via .into() or by naming core::range::Range. A future edition will switch the syntax over. For public APIs that should accept either, take impl RangeBounds<usize>.

Stabilised in Rust 1.96 (May 2026).

233. str::split_terminator — Split Without the Trailing Empty String

"a.b.c.".split('.') hands you a phantom empty string at the end. split_terminator reads the separator as ending each piece, not delimiting it — so the trailing one just vanishes.

Split counts the region after the final separator, even when it’s empty. Feed it text that ends with the separator and you get a ghost element:

1
2
let parts: Vec<&str> = "a.b.c.".split('.').collect();
assert_eq!(parts, ["a", "b", "c", ""]);

That trailing "" is a landmine downstream: an empty record, a blank line, a zero-length field you didn’t ask for.

split_terminator treats the separator as terminating each piece, so a trailing separator produces no empty tail:

1
2
let parts: Vec<&str> = "a.b.c.".split_terminator('.').collect();
assert_eq!(parts, ["a", "b", "c"]);

It only drops the empty piece caused by a trailing separator — interior empties are still real data and still show up:

1
2
let parts: Vec<&str> = "a..b.".split_terminator('.').collect();
assert_eq!(parts, ["a", "", "b"]);

And with no trailing separator it behaves exactly like split:

1
2
let parts: Vec<&str> = "a.b.c".split_terminator('.').collect();
assert_eq!(parts, ["a", "b", "c"]);

Reach for it when parsing records that end with their delimiter — newline-terminated logs, ;-terminated statements, trailing-comma lists — and skip the .filter(|s| !s.is_empty()) cleanup.

#232 Jun 2026

232. Iterator::partition — Split Into Two Collections in One Pass

Splitting items into “matches” and “the rest” with two .filter() calls walks the list twice. partition does it in a single pass.

The two-filter version reads fine but iterates the whole thing twice, once per predicate:

1
2
3
4
let nums = [1, 2, 3, 4, 5, 6, 7, 8];

let even: Vec<i32> = nums.iter().copied().filter(|n| n % 2 == 0).collect();
let odd:  Vec<i32> = nums.iter().copied().filter(|n| n % 2 != 0).collect();

The manual loop is one pass but needs two mutable Vecs and the bookkeeping to feed them. partition is the one-pass version with none of the noise — true items go left, false items go right:

1
2
3
4
5
let nums = [1, 2, 3, 4, 5, 6, 7, 8];

let (even, odd): (Vec<i32>, Vec<i32>) = nums.iter().partition(|&&n| n % 2 == 0);
assert_eq!(even, [2, 4, 6, 8]);
assert_eq!(odd,  [1, 3, 5, 7]);

The collection types come from the annotation, and they don’t have to be Vec or even match each other — any pair that implements FromIterator works:

1
2
3
4
5
6
let words = ["hi", "hello", "yo", "howdy"];

let (long, short): (Vec<&str>, Vec<&str>) =
    words.into_iter().partition(|s| s.len() > 3);
assert_eq!(long,  ["hello", "howdy"]);
assert_eq!(short, ["hi", "yo"]);

Whenever you catch yourself filtering the same iterator twice with opposite conditions, that’s a partition.

#231 Jun 2026

231. slice::chunk_by — Group Consecutive Elements Without the Index Bookkeeping

Splitting a slice into runs of equal (or related) elements usually means a manual loop with a start index and an off-by-one waiting to happen. slice::chunk_by does it in one call.

You want to break [1, 1, 2, 3, 3, 3, 1] into its runs of equal values. The hand-rolled version tracks where each run starts and has to remember to flush the last one:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let data = [1, 1, 2, 3, 3, 3, 1];

let mut groups: Vec<&[i32]> = Vec::new();
let mut start = 0;
for i in 1..=data.len() {
    if i == data.len() || data[i] != data[i - 1] {
        groups.push(&data[start..i]);
        start = i;
    }
}

chunk_by takes a predicate over consecutive pairs and starts a new chunk whenever it returns false:

1
2
3
4
5
6
7
8
let data = [1, 1, 2, 3, 3, 3, 1];

let groups: Vec<&[i32]> = data.chunk_by(|a, b| a == b).collect();

assert_eq!(
    groups,
    [&[1, 1][..], &[2], &[3, 3, 3], &[1]],
);

The predicate is any relation between neighbours, not just equality. Want the ascending runs of a sequence? Split where the order breaks:

1
2
3
4
5
let nums = [1, 3, 5, 2, 4, 1];

let runs: Vec<&[i32]> = nums.chunk_by(|a, b| a <= b).collect();

assert_eq!(runs, [&[1, 3, 5][..], &[2, 4], &[1]]);

Each chunk is a borrowed &[T] into the original slice — no copying. There’s a chunk_by_mut for when you need &mut [T] slices instead. (Stable since Rust 1.77; it was briefly named group_by.)

#230 Jun 2026

230. slice::split_at_mut — Two Mutable Halves, No unsafe, No Borrow Fight

Need a &mut to two different elements of the same slice at once? Indexing twice won’t compile — the borrow checker sees the whole slice borrowed twice. split_at_mut hands you two non-overlapping mutable halves instead.

The borrow checker says no

You want to mutate two elements of a slice in the same expression — say, swap-style logic that reads one and writes another:

1
2
3
4
let mut v = [1, 2, 3, 4];
let a = &mut v[0];
let b = &mut v[3]; // error: cannot borrow `v` as mutable more than once
*a += *b;

The compiler can’t prove v[0] and v[3] are different elements, so it rejects two overlapping &mut borrows of v. Both indexing operations borrow the entire slice.

split_at_mut gives you two disjoint slices

split_at_mut(mid) splits a slice into [0, mid) and [mid, len), returning a &mut to each. Because the halves provably don’t overlap, the borrow checker is happy:

1
2
3
4
let mut v = [1, 2, 3, 4];
let (left, right) = v.split_at_mut(2);
left[0] += right[1]; // v[0] += v[3]
assert_eq!(v, [5, 2, 3, 4]);

Internally it’s one bounds check and a pointer offset — no copying, no allocation. It panics only if mid > len; use split_at_mut_checked to get an Option instead.

Where it shines: in-place algorithms

Reversing a slice by hand needs a read and a write to two indices each step. Split once, walk inward:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn reverse<T>(s: &mut [T]) {
    let n = s.len();
    let (front, back) = s.split_at_mut(n / 2);
    for (a, b) in front.iter_mut().zip(back.iter_mut().rev()) {
        std::mem::swap(a, b);
    }
}

let mut data = [1, 2, 3, 4, 5];
reverse(&mut data);
assert_eq!(data, [5, 4, 3, 2, 1]);

The two iterators borrow disjoint halves, so iterating both mutably at once just works.

Divide and conquer for free

Because each half is itself a &mut [T], recursive algorithms split cleanly — the basis for parallelizing with something like Rayon’s join:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn sum_halves(s: &mut [i32]) -> i32 {
    if s.len() <= 1 {
        return s.first().copied().unwrap_or(0);
    }
    let mid = s.len() / 2;
    let (left, right) = s.split_at_mut(mid);
    sum_halves(left) + sum_halves(right)
}

let mut v = [1, 2, 3, 4, 5];
assert_eq!(sum_halves(&mut v), 15);

Whenever you’re fighting the borrow checker over two mutable spots in one slice, stop reaching for unsafesplit_at_mut is the safe, zero-cost answer. For non-adjacent individual elements, its cousin get_disjoint_mut does the same trick by index.

#229 Jun 2026

229. char::to_digit — Turn a Digit Character Into Its Value, Not Its Byte

Reached for c as u8 - b'0' to turn '7' into 7? It works until the input isn’t a digit — then you get silent garbage. char::to_digit does it safely and handles hex too.

The byte-math trap

The classic trick subtracts the ASCII code of '0':

1
2
3
let c = '7';
let value = c as u8 - b'0';
assert_eq!(value, 7); // fine...

It’s fine for '0'..='9'. But there’s no validation — feed it anything else and the arithmetic just keeps going:

1
2
3
4
5
// 'f' is 102, b'0' is 48 → 54, which is nonsense, not 15
assert_eq!('f' as u8 - b'0', 54);

// 'a' is 97 → 97 - 48 = 49, also wrong
assert_eq!('a' as u8 - b'0', 49);

No panic, no None — just a wrong number flowing downstream.

to_digit returns an Option

char::to_digit(radix) converts a digit character to its numeric value and gives you an Option<u32>, so non-digits become None instead of garbage:

1
2
3
assert_eq!('7'.to_digit(10), Some(7));
assert_eq!('0'.to_digit(10), Some(0));
assert_eq!('x'.to_digit(10), None); // not a digit → None

Pass 16 and it parses hex, letters included and case-insensitive:

1
2
3
4
assert_eq!('f'.to_digit(16), Some(15));
assert_eq!('F'.to_digit(16), Some(15));
assert_eq!('a'.to_digit(16), Some(10));
assert_eq!('9'.to_digit(16), Some(9));

Where it shines

Because it returns an Option, it drops straight into filter_map — sum every digit in a string and skip everything else, no manual bounds check:

1
2
let total: u32 = "a1b2c3".chars().filter_map(|c| c.to_digit(10)).sum();
assert_eq!(total, 6);

The reverse direction

char::from_digit goes the other way — a value plus a radix back to a digit character:

1
2
3
assert_eq!(char::from_digit(15, 16), Some('f'));
assert_eq!(char::from_digit(9, 10), Some('9'));
assert_eq!(char::from_digit(42, 16), None); // out of range → None

Both have been stable since Rust 1.0. Whenever you’re tempted to do char-to-number arithmetic by hand, reach for to_digit — it validates, it does hex, and it never silently lies to you.

#228 Jun 2026

228. Vec::push_mut — Push and Get a &mut Back, Skip the last_mut().unwrap()

Pushed an element and immediately needed to tweak it? The old way was push then last_mut().unwrap(). Rust 1.95 added push_mut, which hands you the &mut right back.

The push-then-refetch dance

You add an element to a Vec, then need a mutable handle to it — to set a field, run a builder step, whatever. push returns (), so you go fishing for what you just put in:

1
2
3
4
5
6
7
struct Span { start: usize, end: usize }

let mut spans = Vec::new();
spans.push(Span { start: 0, end: 0 });
let last = spans.last_mut().unwrap(); // re-fetch what we just pushed
last.end = 10;
assert_eq!(spans[0].end, 10);

That last_mut().unwrap() is pure ceremony. You know it’s there — you pushed it one line ago — but the type system makes you unwrap an Option anyway.

push_mut returns the reference directly

Stabilized in Rust 1.95, Vec::push_mut appends the value and returns a &mut T to it in one step:

1
2
3
4
5
6
struct Span { start: usize, end: usize }

let mut spans = Vec::new();
let last = spans.push_mut(Span { start: 0, end: 0 });
last.end = 10;
assert_eq!(spans[0].end, 10);

No Option, no unwrap, no second lookup. The borrow checker is happy because the returned reference is tied to the Vec.

Where it shines: build-then-mutate loops

Parsing or accumulating state where each new entry gets refined before you move on reads much cleaner:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#[derive(Debug, PartialEq)]
struct Token { text: String, len: usize }

let mut tokens = Vec::new();
for word in "fn main".split_whitespace() {
    let tok = tokens.push_mut(Token { text: word.to_string(), len: 0 });
    tok.len = tok.text.len(); // fill in a derived field in place
}

assert_eq!(tokens[0], Token { text: "fn".into(), len: 2 });
assert_eq!(tokens[1], Token { text: "main".into(), len: 4 });

Not just Vec

The same _mut family landed across the collections: Vec::insert_mut, VecDeque::push_front_mut / push_back_mut / insert_mut, and LinkedList::push_front_mut / push_back_mut all return a &mut to the freshly inserted element:

1
2
3
4
5
6
use std::collections::VecDeque;

let mut q = VecDeque::new();
let front = q.push_front_mut(1);
*front += 41;
assert_eq!(q[0], 42);

Whenever you push and immediately need to touch what you pushed, reach for push_mut — the reference is already in your hand.

#227 Jun 2026

227. trim_matches — Strip the Same Char Off Both Ends, However Many There Are

trim() only knows about whitespace, and strip_prefix peels off one occurrence. When you need to shave every leading and trailing . (or 0, or quote) off a string, reach for trim_matches.

The hand-rolled trim

You’ve got a string padded with some character and want it gone from both ends — but only the ends:

1
2
3
4
5
let s = "***heading***";

// strip_prefix only removes one, and only the front
let once = s.strip_prefix('*').unwrap_or(s);
assert_eq!(once, "**heading***");

Looping strip_prefix/strip_suffix until they stop matching works, but it’s a chore. trim_matches does exactly that for you — it removes all consecutive matches from both ends and leaves the middle alone:

1
2
3
4
5
let s = "***heading***";
assert_eq!(s.trim_matches('*'), "heading");

// only the ends — interior matches stay put
assert_eq!("0x00ff00".trim_matches('0'), "x00ff");

One end at a time

There are directional versions when you only care about one side:

1
2
assert_eq!("--verbose".trim_start_matches("--"), "verbose");
assert_eq!("file.txt.bak".trim_end_matches(".bak"), "file.txt");

The pattern can be a closure or a set of chars

The argument is a Pattern, so you’re not limited to a single char. Pass a closure to trim by predicate, or an array of chars to trim any of them:

1
2
3
4
5
// strip leading digits
assert_eq!("12abc34".trim_start_matches(|c: char| c.is_numeric()), "abc34");

// trim any of several characters
assert_eq!("(value)".trim_matches(['(', ')']), "value");

Note trim_end_matches("--") strips the whole substring repeatedly, not a set of chars — that’s the difference between passing "--" and passing ['-'].