#068 Apr 2026

68. f64::next_up — Walk the Floating Point Number Line

Ever wondered what the next representable floating point number after 1.0 is? Since Rust 1.86, f64::next_up and f64::next_down let you step through the number line one float at a time.

The problem

Floating point numbers aren’t evenly spaced — the gap between representable values grows as the magnitude increases. Before next_up / next_down, figuring out the next neighbor required bit-level manipulation of the IEEE 754 representation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    // The hard way (before 1.86): manually decode bits
    let x: f64 = 1.0;
    let bits = x.to_bits();
    let next_bits = bits + 1;
    let next = f64::from_bits(next_bits);

    assert!(next > x);
    assert_eq!(next, 1.0000000000000002);
}

Error-prone, unreadable, and doesn’t handle edge cases like negative numbers, zero, or special values.

The clean way

next_up returns the smallest f64 greater than self. next_down returns the largest f64 less than self:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    let x: f64 = 1.0;

    let up = x.next_up();
    let down = x.next_down();

    assert!(up > x);
    assert!(down < x);
    assert_eq!(up, 1.0000000000000002);
    assert_eq!(down, 0.9999999999999998);

    // There's no float between x and its neighbors
    assert_eq!(up.next_down(), x);
    assert_eq!(down.next_up(), x);
}

They handle all the edge cases you’d rather not think about — negative numbers, subnormals, and infinity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    // Works across zero
    assert_eq!(0.0_f64.next_up(), 5e-324);   // smallest positive f64
    assert_eq!(0.0_f64.next_down(), -5e-324); // largest negative f64

    // Infinity is the boundary
    assert_eq!(f64::MAX.next_up(), f64::INFINITY);
    assert_eq!(f64::INFINITY.next_up(), f64::INFINITY);

    // NaN stays NaN
    assert!(f64::NAN.next_up().is_nan());
}

Practical use: precision-aware comparisons

The gap between adjacent floats is called an ULP (unit in the last place). next_up lets you build tolerance-aware comparisons without guessing at epsilon values:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn almost_equal(a: f64, b: f64, max_ulps: u32) -> bool {
    if a == b { return true; }

    let mut current = a;
    for _ in 0..max_ulps {
        current = if a < b { current.next_up() } else { current.next_down() };
        if current == b { return true; }
    }
    false
}

fn main() {
    let a = 0.1 + 0.2;
    let b = 0.3;

    // They're not equal...
    assert_ne!(a, b);

    // ...but they're within 1 ULP of each other
    assert!(almost_equal(a, b, 1));
}

Also available on f32 with the same API. These methods are const fn, so you can use them in const contexts too.

#067 Apr 2026

67. Box::leak — Turn Owned Data Into a Static Reference

Sometimes you need a &'static str but all you have is a String. Meet Box::leak — it deliberately leaks heap memory so you get a reference that lives forever.

The problem

Many APIs demand &'static str — logging frameworks, CLI argument definitions, thread names, and error messages baked into types. But your data is often dynamic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
fn set_thread_name(name: &'static str) {
    // Imagine this requires a 'static lifetime
    println!("Thread: {name}");
}

fn main() {
    let prefix = "worker";
    let id = 42;
    let name = format!("{prefix}-{id}");

    // This won't compile — name is a String, not &'static str
    // set_thread_name(&name);

    // Workaround: leak it
    let name: &'static str = Box::leak(name.into_boxed_str());
    set_thread_name(name);
}

How it works

Box::leak consumes the Box and returns a mutable reference with a 'static lifetime. The memory stays allocated on the heap but is never freed — it “leaks” on purpose:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
    // Leak a String into &'static str
    let s: &'static str = Box::leak(String::from("hello").into_boxed_str());
    assert_eq!(s, "hello");

    // Leak a Vec into &'static [T]
    let nums: &'static [i32] = Box::leak(vec![1, 2, 3].into_boxed_slice());
    assert_eq!(nums, &[1, 2, 3]);

    // Leak any type into &'static T
    let val: &'static mut i32 = Box::leak(Box::new(99));
    *val += 1;
    assert_eq!(*val, 100);
}

The pattern is always the same: put your data in a Box, then call leak() to trade ownership for a 'static reference.

When it makes sense

Box::leak shines for data that truly lives for the entire program — configuration, lookup tables, or singleton-style values initialized once at startup:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
struct Config {
    db_url: String,
    max_connections: usize,
}

fn init_config() -> &'static Config {
    let config = Config {
        db_url: String::from("postgres://localhost/mydb"),
        max_connections: 10,
    };
    Box::leak(Box::new(config))
}

fn main() {
    let config = init_config();

    // Now any function can take &Config without lifetime gymnastics
    assert_eq!(config.max_connections, 10);
    assert!(config.db_url.contains("localhost"));
}

No Arc, no lazy_static!, no lifetime parameters threading through your call stack — just a plain reference.

The catch: you can’t unleak

The memory is genuinely leaked. It won’t be freed until the process exits. This is fine for startup-time singletons but a bad idea in a loop:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    // DON'T do this — leaks memory every iteration
    // for i in 0..1000 {
    //     let s: &'static str = Box::leak(format!("item-{i}").into_boxed_str());
    // }

    // If you need to reclaim the memory, you can "unleak" with Box::from_raw:
    let leaked: &'static mut String = Box::leak(Box::new(String::from("temporary")));
    let ptr: *mut String = leaked;

    // SAFETY: ptr came from Box::leak and hasn't been freed
    let reclaimed: Box<String> = unsafe { Box::from_raw(ptr) };
    assert_eq!(*reclaimed, "temporary");
    // reclaimed is dropped here — memory freed
}

Use Box::leak when the data truly needs to outlive everything. For anything else, reach for Arc, OnceLock, or scoped lifetimes instead.

66. Inferred Const Generics — Let the Compiler Count For You

Tired of manually counting array lengths and const generic arguments? Since Rust 1.89, you can write _ in const generic positions and let the compiler figure it out.

The annoyance

Whenever you work with arrays or const generics, you end up counting elements by hand:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let rgb: [u8; 3] = [255, 128, 0];
    let matrix: [[f64; 3]; 2] = [
        [1.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
    ];

    assert_eq!(rgb.len(), 3);
    assert_eq!(matrix.len(), 2);
}

Change the data, forget to update the length, and you get a compile error — or worse, you waste time counting elements that the compiler already knows.

Enter _ for const generics

Now you can replace const generic arguments with _ wherever the compiler can infer the value:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let rgb: [u8; _] = [255, 128, 0];
    let matrix: [[f64; _]; _] = [
        [1.0, 0.0, 0.0],
        [0.0, 1.0, 0.0],
    ];

    assert_eq!(rgb.len(), 3);
    assert_eq!(matrix.len(), 2);
    assert_eq!(matrix[0].len(), 3);
}

The compiler sees 3 elements and fills in the 3 for you. Add a fourth element tomorrow, and the type updates automatically — no manual bookkeeping.

Array repeat expressions

The _ also works in repeat expressions, so you can define how many times to repeat without hardcoding the count:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn zeros<const N: usize>() -> [f64; N] {
    [0.0; _]
}

fn main() {
    let small: [f64; 3] = zeros();
    let big: [f64; 8] = zeros();

    assert_eq!(small, [0.0, 0.0, 0.0]);
    assert_eq!(big.len(), 8);
}

Inside zeros(), [0.0; _] infers the repeat count from the return type’s N. No need to write [0.0; N] — though you still can.

Works with your own const generics too

Any function with const generic parameters can benefit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn chunk_sum<const N: usize>(arr: [i32; N]) -> i32 {
    arr.iter().sum()
}

fn main() {
    // The compiler infers N = 4 from the array literal
    let total = chunk_sum([10, 20, 30, 40]);
    assert_eq!(total, 100);

    // Explicit _ works too when you want to be clear about inference
    let arr: [i32; _] = [1, 2, 3, 4, 5];
    let total = chunk_sum(arr);
    assert_eq!(total, 15);
}

Where you can’t use _

One important restriction: inferred const generics are only allowed in expressions, not in item signatures. You can’t write this:

1
2
// This does NOT compile — _ not allowed in function signatures
// fn make_pairs() -> [(&str, i32); _] { ... }

The compiler needs concrete types in signatures. Use _ inside function bodies and let bindings where the compiler has enough context to infer.

A small quality-of-life win that removes one more source of busywork. Next time you’re stacking array literals, let the compiler do the counting.

65. Precise Capturing — Stop impl Trait From Borrowing Too Much

Ever had the compiler refuse to let you use a value after calling a function — even though the return type shouldn’t borrow it? use<> bounds give you precise control over what impl Trait actually captures.

The overcapturing problem

In Rust 2024, impl Trait in return position captures all in-scope generic parameters by default — including lifetimes you never intended to hold onto.

Here’s where it bites:

1
2
3
fn make_greeting(name: &str) -> impl std::fmt::Display + use<> {
    format!("Hello, {name}!")
}

Without the use<> bound, the returned impl Display would capture the &str lifetime — even though format! produces an owned String that doesn’t borrow name at all. That means the caller can’t drop or reuse name while the return value is alive, for no good reason.

Enter use<> bounds

Stabilized in Rust 1.82, the use<> syntax lets you explicitly declare which generic parameters the opaque type is allowed to capture:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn make_greeting(name: &str) -> impl std::fmt::Display + use<> {
    format!("Hello, {name}!")
}

fn main() {
    let mut name = String::from("world");
    let greeting = make_greeting(&name);

    // This works! The greeting doesn't capture the lifetime of `name`
    name.push_str("!!!");

    println!("{greeting}"); // Hello, world!
    println!("{name}");     // world!!!
}

use<> means “capture nothing” — the return type is fully owned and independent of any input lifetimes.

Selective capturing

You can also pick exactly which lifetimes to capture. Consider a function that takes two references but only needs to hold onto one:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn pick_label<'a, 'b>(
    label: &'a str,
    _config: &'b str,
) -> impl std::fmt::Display + use<'a> {
    // We use `_config` to decide formatting, but
    // the return value only borrows from `label`
    format!("[{label}]")
}

fn main() {
    let label = String::from("status");
    let mut config = String::from("uppercase");

    let display = pick_label(&label, &config);

    // We can mutate `config` — the return value doesn't borrow it
    config.push_str(":bold");

    assert_eq!(format!("{display}"), "[status]");
    assert_eq!(config, "uppercase:bold");
}

use<'a> says “this return type borrows from 'a but is independent of 'b.” Without it, the compiler assumes the opaque type captures both lifetimes, preventing you from reusing config.

When you need this

The use<> bound shines when you’re returning iterators, closures, or other impl Trait types from functions that take references they don’t actually need to hold:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn make_counter(start: &str) -> impl Iterator<Item = usize> + use<> {
    let n: usize = start.parse().unwrap_or(0);
    (n..).take(5)
}

fn main() {
    let mut input = String::from("3");
    let counter = make_counter(&input);

    // We can mutate `input` because the iterator doesn't borrow it
    input.clear();

    let values: Vec<usize> = counter.collect();
    assert_eq!(values, vec![3, 4, 5, 6, 7]);
}

A small annotation that unlocks flexibility the compiler couldn’t infer on its own. If you’ve upgraded to Rust 2024 and hit mysterious “borrowed value does not live long enough” errors on impl Trait returns, use<> is likely the fix.

64. Iterator::unzip — Split Pairs into Separate Collections

Got an iterator of tuples and need two separate collections? Stop looping and pushing manually — unzip splits pairs into two collections in a single pass.

The problem

You have an iterator that yields pairs — maybe key-value tuples from a computation, or results from enumerate. You need two separate Vecs. The manual approach works, but it’s noisy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let pairs = vec![("Alice", 95), ("Bob", 87), ("Carol", 92)];

let mut names = Vec::new();
let mut scores = Vec::new();
for (name, score) in &pairs {
    names.push(*name);
    scores.push(*score);
}

assert_eq!(names, vec!["Alice", "Bob", "Carol"]);
assert_eq!(scores, vec![95, 87, 92]);

Two mutable Vecs, a loop, manual pushes — all to split pairs apart.

The fix

Iterator::unzip does exactly this in one line:

1
2
3
4
5
6
let pairs = vec![("Alice", 95), ("Bob", 87), ("Carol", 92)];

let (names, scores): (Vec<&str>, Vec<i32>) = pairs.into_iter().unzip();

assert_eq!(names, vec!["Alice", "Bob", "Carol"]);
assert_eq!(scores, vec![95, 87, 92]);

The type annotation on the left tells Rust which collections to build. It works with any types that implement Default + Extend — so Vec, String, HashSet, and more.

Works great with enumerate

Need indices and values in separate collections?

1
2
3
4
5
6
let fruits = vec!["apple", "banana", "cherry"];

let (indices, items): (Vec<usize>, Vec<&&str>) = fruits.iter().enumerate().unzip();

assert_eq!(indices, vec![0, 1, 2]);
assert_eq!(items, vec![&"apple", &"banana", &"cherry"]);

Combine with map for transforms

Chain map before unzip to transform on the fly:

1
2
3
4
5
6
7
8
9
let data = vec![("temp_c", 20.0), ("temp_c", 35.0), ("temp_c", 0.0)];

let (labels, fahrenheit): (Vec<&str>, Vec<f64>) = data
    .into_iter()
    .map(|(label, c)| (label, c * 9.0 / 5.0 + 32.0))
    .unzip();

assert_eq!(labels, vec!["temp_c", "temp_c", "temp_c"]);
assert_eq!(fahrenheit, vec![68.0, 95.0, 32.0]);

Unzip into different collection types

Since unzip works with any Default + Extend types, you can collect into mixed collections:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::collections::HashSet;

let entries = vec![("admin", "read"), ("admin", "write"), ("user", "read")];

let (roles, perms): (Vec<&str>, HashSet<&str>) = entries.into_iter().unzip();

assert_eq!(roles, vec!["admin", "admin", "user"]);
assert_eq!(perms.len(), 2); // "read" and "write", deduplicated
assert!(perms.contains("read"));
assert!(perms.contains("write"));

One side is a Vec, the other is a HashSetunzip doesn’t care, as long as both sides can extend themselves.

Whenever you’re about to write a loop that pushes into two collections, reach for unzip instead.

63. fmt::from_fn — Display Anything With a Closure

Need a quick Display impl without defining a whole new type? std::fmt::from_fn turns any closure into a formattable value — ad-hoc Display in one line.

The problem: Display needs a type

Normally, implementing Display means creating a wrapper struct just to control how something gets formatted:

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

struct CommaSeparated<'a>(&'a [i32]);

impl fmt::Display for CommaSeparated<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (i, val) in self.0.iter().enumerate() {
            if i > 0 { write!(f, ", ")?; }
            write!(f, "{val}")?;
        }
        Ok(())
    }
}

fn main() {
    let nums = vec![1, 2, 3];
    let display = CommaSeparated(&nums);
    assert_eq!(display.to_string(), "1, 2, 3");
}

That’s a lot of boilerplate for “join with commas.”

After: fmt::from_fn

Stabilized in Rust 1.93, std::fmt::from_fn takes a closure Fn(&mut Formatter) -> fmt::Result and returns a value that implements Display (and Debug):

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

fn main() {
    let nums = vec![1, 2, 3];

    let display = fmt::from_fn(|f| {
        for (i, val) in nums.iter().enumerate() {
            if i > 0 { write!(f, ", ")?; }
            write!(f, "{val}")?;
        }
        Ok(())
    });

    assert_eq!(display.to_string(), "1, 2, 3");
    // Works directly in format strings too
    assert_eq!(format!("numbers: {display}"), "numbers: 1, 2, 3");
}

No wrapper type, no trait impl — just a closure that writes to a formatter.

Building reusable formatters

Wrap from_fn in a function and you have a reusable formatter without the boilerplate:

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

fn join_with<'a, I, T>(iter: I, sep: &'a str) -> impl fmt::Display + 'a
where
    I: IntoIterator<Item = T> + 'a,
    T: fmt::Display + 'a,
{
    fmt::from_fn(move |f| {
        let mut first = true;
        for item in iter {
            if !first { write!(f, "{sep}")?; }
            first = false;
            write!(f, "{item}")?;
        }
        Ok(())
    })
}

fn main() {
    let tags = vec!["rust", "formatting", "closures"];
    assert_eq!(join_with(tags, " | ").to_string(), "rust | formatting | closures");

    let nums = vec![10, 20, 30];
    assert_eq!(format!("sum of [{}]", join_with(nums, " + ")), "sum of [10 + 20 + 30]");
}

Lazy formatting — no allocation until needed

from_fn is lazy — the closure only runs when the value is actually formatted. This makes it perfect for logging where most messages might be filtered out:

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

fn debug_summary(data: &[u8]) -> impl fmt::Display + '_ {
    fmt::from_fn(move |f| {
        write!(f, "[{} bytes: ", data.len())?;
        for (i, byte) in data.iter().take(4).enumerate() {
            if i > 0 { write!(f, " ")?; }
            write!(f, "{byte:02x}")?;
        }
        if data.len() > 4 { write!(f, " ...")?; }
        write!(f, "]")
    })
}

fn main() {
    let payload = vec![0xDE, 0xAD, 0xBE, 0xEF, 0x42, 0x00];
    let summary = debug_summary(&payload);

    // The closure hasn't run yet — zero work done
    // It only formats when you actually use it:
    assert_eq!(summary.to_string(), "[6 bytes: de ad be ef ...]");
}

No String is allocated unless something actually calls Display::fmt. Compare that to eagerly building a debug string just in case.

from_fn also implements Debug

The returned value implements both Display and Debug, so it works with {:?} too:

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

fn main() {
    let val = fmt::from_fn(|f| write!(f, "custom"));
    assert_eq!(format!("{val}"),   "custom");
    assert_eq!(format!("{val:?}"), "custom");
}

fmt::from_fn is a tiny addition to std that removes a surprisingly common friction point — any time you’d reach for a newtype just to implement Display, try a closure instead.

#062 Apr 2026

62. Iterator::flat_map — Map and Flatten in One Step

Need to transform each element into multiple items and collect them all into a flat sequence? flat_map combines map and flatten into a single, expressive call.

The nested iterator problem

Say you have a list of sentences and want all individual words. The naive approach with map gives you an iterator of iterators:

1
2
3
4
5
6
7
8
let sentences = vec!["hello world", "foo bar baz"];

// map gives us an iterator of Split iterators — not what we want
let nested: Vec<Vec<&str>> = sentences.iter()
    .map(|s| s.split_whitespace().collect())
    .collect();

assert_eq!(nested, vec![vec!["hello", "world"], vec!["foo", "bar", "baz"]]);

You could chain .map().flatten(), but flat_map does both at once:

1
2
3
4
5
6
7
let sentences = vec!["hello world", "foo bar baz"];

let words: Vec<&str> = sentences.iter()
    .flat_map(|s| s.split_whitespace())
    .collect();

assert_eq!(words, vec!["hello", "world", "foo", "bar", "baz"]);

Expanding one-to-many relationships

flat_map shines when each input element maps to zero or more outputs. Think of it as a one-to-many transform:

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

// Each number expands to itself and its double
let expanded: Vec<i32> = numbers.iter()
    .flat_map(|&n| vec![n, n * 2])
    .collect();

assert_eq!(expanded, vec![1, 2, 2, 4, 3, 6]);

Filtering and transforming at once

Since flat_map’s closure can return an empty iterator, it naturally combines filtering and mapping — just return None or Some:

1
2
3
4
5
6
7
let inputs = vec!["42", "not_a_number", "7", "oops", "13"];

let parsed: Vec<i32> = inputs.iter()
    .flat_map(|s| s.parse::<i32>().ok())
    .collect();

assert_eq!(parsed, vec![42, 7, 13]);

This works because Option implements IntoIteratorSome(x) yields one item, None yields zero. It’s equivalent to filter_map, but flat_map generalizes to any iterator, not just Option.

Traversing nested structures

Got a tree-like structure? flat_map lets you drill into children naturally:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct Team {
    name: &'static str,
    members: Vec<&'static str>,
}

let teams = vec![
    Team { name: "backend", members: vec!["Alice", "Bob"] },
    Team { name: "frontend", members: vec!["Carol"] },
    Team { name: "devops", members: vec!["Dave", "Eve", "Frank"] },
];

let all_members: Vec<&str> = teams.iter()
    .flat_map(|team| team.members.iter().copied())
    .collect();

assert_eq!(all_members, vec!["Alice", "Bob", "Carol", "Dave", "Eve", "Frank"]);

flat_map vs map + flatten

They’re semantically identical — flat_map(f) is just map(f).flatten(). But flat_map reads better and signals your intent: “each element produces multiple items, and I want them all in one sequence.”

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

// These are equivalent:
let a: Vec<i32> = data.iter().flat_map(|v| v.iter().copied()).collect();
let b: Vec<i32> = data.iter().map(|v| v.iter().copied()).flatten().collect();

assert_eq!(a, b);
assert_eq!(a, vec![1, 2, 3, 4, 5, 6]);

flat_map has been stable since Rust 1.0 — it’s a fundamental iterator combinator that replaces nested loops with clean, composable pipelines.

#061 Apr 2026

61. Iterator::reduce — Fold Without an Initial Value

Using fold but your accumulator starts as the first element anyway? Iterator::reduce cuts out the boilerplate and handles empty iterators gracefully.

The fold pattern you keep writing

When finding the longest string, maximum value, or combining elements, fold forces you to pick an initial value — often awkwardly:

1
2
3
4
5
6
7
let words = vec!["rust", "is", "awesome"];

let longest = words.iter().fold("", |acc, &w| {
    if w.len() > acc.len() { w } else { acc }
});

assert_eq!(longest, "awesome");

That empty string "" is a code smell — it’s not a real element, it’s just satisfying fold’s signature. And if the input is empty, you silently get "" back instead of knowing there was nothing to reduce.

Enter reduce

Iterator::reduce uses the first element as the initial accumulator. No seed value needed, and it returns Option<T>None for empty iterators:

1
2
3
4
5
6
7
let words = vec!["rust", "is", "awesome"];

let longest = words.iter().reduce(|acc, w| {
    if w.len() > acc.len() { w } else { acc }
});

assert_eq!(longest, Some(&"awesome"));

The Option return makes the empty case explicit — no more silent defaults.

Finding extremes without max_by

reduce is perfect for custom comparisons where max_by feels heavy:

1
2
3
4
5
6
7
let scores = vec![("Alice", 92), ("Bob", 87), ("Carol", 95), ("Dave", 88)];

let top_scorer = scores.iter().reduce(|best, current| {
    if current.1 > best.1 { current } else { best }
});

assert_eq!(top_scorer, Some(&("Carol", 95)));

Concatenating without an allocator seed

Building a combined result from parts? reduce avoids allocating an empty starter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let parts = vec![
    String::from("hello"),
    String::from(" "),
    String::from("world"),
];

let combined = parts.into_iter().reduce(|mut acc, s| {
    acc.push_str(&s);
    acc
});

assert_eq!(combined, Some(String::from("hello world")));

Compare this to fold(String::new(), ...) — with reduce, the first String becomes the accumulator directly, saving one allocation.

reduce vs fold — when to use which

Use reduce when the accumulator is the same type as the elements and there’s no meaningful “zero” value. Use fold when you need a different return type or a specific starting value:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// reduce: same type in, same type out
let sum = vec![1, 2, 3, 4].into_iter().reduce(|a, b| a + b);
assert_eq!(sum, Some(10));

// fold: different return type (counting into a HashMap)
use std::collections::HashMap;
let counts = vec!["a", "b", "a", "c", "b", "a"]
    .into_iter()
    .fold(HashMap::new(), |mut map, item| {
        *map.entry(item).or_insert(0) += 1;
        map
    });
assert_eq!(counts["a"], 3);

reduce has been stable since Rust 1.51 — it’s the functional programmer’s best friend for collapsing iterators when the first element is your natural starting point.

60. Iterator::partition — Split a Collection in Two

Need to split a collection into two groups based on a condition? Skip the manual loop — Iterator::partition does it in one call.

The manual way

Without partition, you’d loop and push into two separate vectors:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];

let mut evens = Vec::new();
let mut odds = Vec::new();

for n in numbers {
    if n % 2 == 0 {
        evens.push(n);
    } else {
        odds.push(n);
    }
}

assert_eq!(evens, vec![2, 4, 6, 8]);
assert_eq!(odds, vec![1, 3, 5, 7]);

It works, but it’s a lot of ceremony for a simple split.

Enter partition

Iterator::partition collects into two collections in a single pass. Items where the predicate returns true go left, false goes right:

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

let (evens, odds): (Vec<_>, Vec<_>) = numbers
    .iter()
    .partition(|&&n| n % 2 == 0);

assert_eq!(evens, vec![&2, &4, &6, &8]);
assert_eq!(odds, vec![&1, &3, &5, &7]);

The type annotation (Vec<_>, Vec<_>) is required — Rust needs to know what collections to build. You can partition into any type that implements Default + Extend, not just Vec.

Owned values with into_iter

Use into_iter() when you want owned values instead of references:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let files = vec![
    "main.rs", "lib.rs", "test_utils.rs",
    "README.md", "CHANGELOG.md",
];

let (rust_files, other_files): (Vec<_>, Vec<_>) = files
    .into_iter()
    .partition(|f| f.ends_with(".rs"));

assert_eq!(rust_files, vec!["main.rs", "lib.rs", "test_utils.rs"]);
assert_eq!(other_files, vec!["README.md", "CHANGELOG.md"]);

A practical use: triaging results

partition pairs beautifully with Result to separate successes from failures:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let inputs = vec!["42", "not_a_number", "7", "oops", "13"];

let (oks, errs): (Vec<_>, Vec<_>) = inputs
    .iter()
    .map(|s| s.parse::<i32>())
    .partition(Result::is_ok);

let values: Vec<i32> = oks.into_iter().map(Result::unwrap).collect();
let failures: Vec<_> = errs.into_iter().map(Result::unwrap_err).collect();

assert_eq!(values, vec![42, 7, 13]);
assert_eq!(failures.len(), 2);

partition has been stable since Rust 1.0 — one of those hidden gems that’s been there all along. Anytime you reach for a loop to split items into two buckets, reach for partition instead.

#059 Apr 2026

59. split_first_chunk — Destructure Slices into Arrays

Parsing a header from a byte buffer? Extracting the first N elements of a slice? split_first_chunk hands you a fixed-size array and the remainder in one call — no manual indexing, no panics.

The Problem

You have a byte slice and need to pull out a fixed-size prefix — say a 4-byte magic number or a 2-byte length field. The manual approach is fragile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn parse_header(data: &[u8]) -> Option<([u8; 4], &[u8])> {
    if data.len() < 4 {
        return None;
    }
    let header: [u8; 4] = data[..4].try_into().unwrap();
    let rest = &data[4..];
    Some((header, rest))
}

fn main() {
    let packet = b"RUST is awesome";
    let (header, rest) = parse_header(packet).unwrap();
    assert_eq!(&header, b"RUST");
    assert_eq!(rest, b" is awesome");
}

That try_into().unwrap() is ugly, and if you get the index arithmetic wrong, you get a panic at runtime.

After: split_first_chunk

Stabilized in Rust 1.77, split_first_chunk splits a slice into a &[T; N] array reference and the remaining slice — returning None if the slice is too short:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn parse_header(data: &[u8]) -> Option<(&[u8; 4], &[u8])> {
    data.split_first_chunk::<4>()
}

fn main() {
    let packet = b"RUST is awesome";
    let (magic, rest) = parse_header(packet).unwrap();
    assert_eq!(magic, b"RUST");
    assert_eq!(rest, b" is awesome");

    // Too short — returns None instead of panicking
    let tiny = b"RS";
    assert!(tiny.split_first_chunk::<4>().is_none());
}

One method call. No manual slicing, no try_into, and the const generic N ensures the compiler knows the exact array size.

Chaining Chunks for Protocol Parsing

Real protocols have multiple fields. Chain split_first_chunk calls to peel them off one at a time:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn parse_packet(data: &[u8]) -> Option<([u8; 2], [u8; 4], &[u8])> {
    let (version, rest) = data.split_first_chunk::<2>()?;
    let (length, payload) = rest.split_first_chunk::<4>()?;
    Some((*version, *length, payload))
}

fn main() {
    let raw = b"\x01\x02\x00\x00\x00\x05hello";
    let (version, length, payload) = parse_packet(raw).unwrap();

    assert_eq!(version, [0x01, 0x02]);
    assert_eq!(length, [0x00, 0x00, 0x00, 0x05]);
    assert_eq!(payload, b"hello");
}

Each ? short-circuits if the remaining data is too short. No bounds checks scattered across your code.

From the Other End: split_last_chunk

Need to grab a suffix instead — like a trailing checksum? split_last_chunk mirrors the API from the back:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn strip_checksum(data: &[u8]) -> Option<(&[u8], &[u8; 2])> {
    data.split_last_chunk::<2>()
}

fn main() {
    let msg = b"payload\xAB\xCD";
    let (body, checksum) = strip_checksum(msg).unwrap();
    assert_eq!(body, b"payload");
    assert_eq!(checksum, &[0xAB, 0xCD]);

    let short = b"\x01";
    assert!(strip_checksum(short).is_none());
}

Same safety, same ergonomics — just peeling from the tail.

The Full Family

These methods come in mutable variants too, all stabilized in 1.77:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
fn main() {
    // Immutable — borrow array refs from a slice
    let data: &[u8] = &[1, 2, 3, 4, 5];
    let (head, tail) = data.split_first_chunk::<2>().unwrap();
    assert_eq!(head, &[1, 2]);
    assert_eq!(tail, &[3, 4, 5]);

    // split_last_chunk — from the back
    let (init, last) = data.split_last_chunk::<2>().unwrap();
    assert_eq!(init, &[1, 2, 3]);
    assert_eq!(last, &[4, 5]);

    // first_chunk / last_chunk — just the array, no remainder
    let first: &[u8; 3] = data.first_chunk::<3>().unwrap();
    assert_eq!(first, &[1, 2, 3]);

    let last: &[u8; 3] = data.last_chunk::<3>().unwrap();
    assert_eq!(last, &[3, 4, 5]);
}

Wherever you reach for &data[..N] and a try_into(), there’s probably a chunk method that does it better. Type-safe, bounds-checked, and zero-cost.