#077 Apr 2026

77. repeat_n — Repeat a Value Exactly N Times

Stop writing repeat(x).take(n) — there’s a dedicated function that’s both cleaner and more efficient.

The old way

If you wanted an iterator that yields a value a fixed number of times, you’d chain repeat with take:

1
2
3
4
use std::iter;

let greetings: Vec<_> = iter::repeat("hello").take(3).collect();
assert_eq!(greetings, vec!["hello", "hello", "hello"]);

This works fine for Copy types, but it always clones the value — even on the last iteration, where you could just move it instead.

Enter repeat_n

std::iter::repeat_n does exactly what the name says — repeats a value n times:

1
2
3
4
use std::iter;

let greetings: Vec<_> = iter::repeat_n("hello", 3).collect();
assert_eq!(greetings, vec!["hello", "hello", "hello"]);

Cleaner, more readable, and it comes with a hidden superpower.

The efficiency win

repeat_n moves the value on the last iteration instead of cloning it. This matters when cloning is expensive:

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

let original = vec![1, 2, 3]; // expensive to clone
let copies: Vec<Vec<i32>> = iter::repeat_n(original, 3).collect();

assert_eq!(copies.len(), 3);
assert_eq!(copies[0], vec![1, 2, 3]);
assert_eq!(copies[1], vec![1, 2, 3]);
assert_eq!(copies[2], vec![1, 2, 3]);
// The first two are clones, but the third one is a move — one fewer allocation!

With repeat(x).take(n), you’d clone all n times. With repeat_n, you save one clone. For large buffers or complex types, that’s a meaningful win.

repeat_n with zero

Passing n = 0 yields an empty iterator, and the value is simply dropped — no clones happen at all:

1
2
3
4
use std::iter;

let items: Vec<String> = iter::repeat_n(String::from("unused"), 0).collect();
assert!(items.is_empty());

When to reach for it

Use repeat_n whenever you need a fixed number of identical values. Common patterns:

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

// Initialize a grid row
let row: Vec<f64> = iter::repeat_n(0.0, 10).collect();
assert_eq!(row.len(), 10);

// Pad a sequence
let mut data = vec![1, 2, 3];
data.extend(iter::repeat_n(0, 5));
assert_eq!(data, vec![1, 2, 3, 0, 0, 0, 0, 0]);

Small change, but it makes intent crystal clear: you want exactly n copies, no more, no less.

76. slice::as_chunks — Split Slices into Fixed-Size Arrays

You’re calling .chunks(4) and immediately doing chunk.try_into().unwrap() to get an array. as_chunks gives you &[[T; N]] directly — a slice of properly typed arrays, plus the remainder.

The Problem

When you use .chunks(N), each chunk is a &[T] — a dynamically sized slice. The compiler doesn’t know its length, so you’re stuck converting manually:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn sum_pairs(data: &[i32]) -> Vec<i32> {
    data.chunks(2)
        .filter(|c| c.len() == 2) // skip incomplete last chunk
        .map(|c| c[0] + c[1])     // runtime indexing, no guarantees
        .collect()
}

fn main() {
    let values = [1, 2, 3, 4, 5];
    assert_eq!(sum_pairs(&values), vec![3, 7]);
}

That works, but the compiler can’t verify your index access at compile time. You’re also throwing away the last chunk if it’s incomplete, with no easy way to inspect it.

After: as_chunks

Stabilized in Rust 1.88, as_chunks splits a slice into a &[[T; N]] and a remainder &[T] in one call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn sum_pairs(data: &[i32]) -> Vec<i32> {
    let (chunks, _remainder) = data.as_chunks::<2>();
    chunks.iter().map(|[a, b]| a + b).collect()
}

fn main() {
    let values = [1, 2, 3, 4, 5];
    assert_eq!(sum_pairs(&values), vec![3, 7]);

    // The remainder is available too
    let (chunks, remainder) = values.as_chunks::<2>();
    assert_eq!(chunks, &[[1, 2], [3, 4]]);
    assert_eq!(remainder, &[5]);
}

Each chunk is &[i32; 2], so you can pattern-match [a, b] directly. The compiler knows the size — no bounds checks, no panics.

Processing Fixed-Width Records

Parsing binary data with fixed-width fields is where as_chunks shines. Imagine RGB pixel data packed as bytes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn brighten(pixels: &mut [u8], amount: u8) {
    let (chunks, _) = pixels.as_chunks_mut::<3>();
    for [r, g, b] in chunks {
        *r = r.saturating_add(amount);
        *g = g.saturating_add(amount);
        *b = b.saturating_add(amount);
    }
}

fn main() {
    let mut pixels = [100, 150, 200, 50, 60, 70, 255, 128, 0];
    brighten(&mut pixels, 30);
    assert_eq!(pixels, [130, 180, 230, 80, 90, 100, 255, 158, 30]);
}

No manual stride arithmetic. Each iteration gives you exactly 3 bytes, pattern-matched into r, g, b.

Don’t Lose the Remainder

Unlike chunks_exact() where you call .remainder() on the iterator after consuming it, as_chunks returns the remainder upfront:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let data = [10, 20, 30, 40, 50, 60, 70];

    let (fours, rest) = data.as_chunks::<4>();
    assert_eq!(fours.len(), 1);       // one complete chunk: [10, 20, 30, 40]
    assert_eq!(fours[0], [10, 20, 30, 40]);
    assert_eq!(rest, &[50, 60, 70]);  // leftover elements

    // as_rchunks starts from the right instead
    let (rest, fours) = data.as_rchunks::<4>();
    assert_eq!(rest, &[10, 20, 30]);
    assert_eq!(fours[0], [40, 50, 60, 70]);
}

as_rchunks is the mirror — it aligns chunks to the end, putting the remainder at the front. Useful when your trailing data is the structured part (e.g., a checksum or footer).

The Full Family

All stabilized in Rust 1.88, these come in immutable and mutable variants:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
    let data: &[u8] = &[1, 2, 3, 4, 5, 6, 7];

    // as_chunks — align from left, remainder on right
    let (chunks, rem) = data.as_chunks::<3>();
    assert_eq!(chunks, &[[1, 2, 3], [4, 5, 6]]);
    assert_eq!(rem, &[7]);

    // as_rchunks — align from right, remainder on left
    let (rem, chunks) = data.as_rchunks::<3>();
    assert_eq!(rem, &[1]);
    assert_eq!(chunks, &[[2, 3, 4], [5, 6, 7]]);

    // Mutable versions: as_chunks_mut, as_rchunks_mut
    let mut buf = [0u8; 6];
    let (chunks, _) = buf.as_chunks_mut::<2>();
    chunks[0] = [0xCA, 0xFE];
    chunks[1] = [0xBA, 0xBE];
    chunks[2] = [0xDE, 0xAD];
    assert_eq!(buf, [0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD]);
}

Whenever you’re reaching for .chunks(N) with a compile-time constant, as_chunks::<N>() gives you stronger types, better ergonomics, and the remainder without gymnastics.

75. select_nth_unstable — Find the Kth Element Without Sorting

You’re sorting an entire Vec just to grab the median or the top 3 elements. select_nth_unstable does it in O(n) — no full sort required.

The classic approach: sort everything, then index:

1
2
3
4
let mut scores = vec![82, 45, 99, 67, 73, 91, 55];
scores.sort();
let median = scores[scores.len() / 2];
assert_eq!(median, 73);

That’s O(n log n) to answer an O(n) question. select_nth_unstable uses a partial-sort algorithm (quickselect) to put the element at a given index into its final sorted position — everything before it is smaller or equal, everything after is greater or equal:

1
2
3
4
let mut scores = vec![82, 45, 99, 67, 73, 91, 55];
let mid = scores.len() / 2;
scores.select_nth_unstable(mid);
assert_eq!(scores[mid], 73);

The method returns three mutable slices — elements below, the pivot element, and elements above — so you can work with each partition directly:

1
2
3
4
5
6
7
let mut data = vec![10, 80, 30, 90, 50, 70, 20];
let (lower, median, upper) = data.select_nth_unstable(3);
// lower contains 3 elements all <= *median
// upper contains 3 elements all >= *median
assert_eq!(*median, 50);
assert!(lower.iter().all(|&x| x <= 50));
assert!(upper.iter().all(|&x| x >= 50));

Need the top 3 scores without sorting the full list? Select the boundary, then sort only the small tail:

1
2
3
4
5
6
let mut scores = vec![82, 45, 99, 67, 73, 91, 55];
let k = scores.len() - 3;
scores.select_nth_unstable(k);
let top_3 = &mut scores[k..];
top_3.sort_unstable(); // sort only 3 elements, not all 7
assert_eq!(top_3, &[82, 91, 99]);

Custom ordering works too. Find the median by absolute value:

1
2
3
4
let mut vals = vec![-20, 5, -10, 15, -3, 8, 1];
let mid = vals.len() / 2;
vals.select_nth_unstable_by_key(mid, |v| v.abs());
assert_eq!(vals[mid].abs(), 8);

The “unstable” in the name means equal elements might be reordered (like sort_unstable) — it doesn’t mean the API is experimental. This is stable since Rust 1.49, available on any [T] where T: Ord.

#074 Apr 2026

74. slice::is_sorted — Ask the Slice if It's Already Sorted

You’ve written windows(2).all(|w| w[0] <= w[1]) one too many times. The is_sorted family of methods says what you actually mean — in one call.

Checking whether data is already in order used to mean rolling your own predicate:

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

// The old way — correct but noisy:
let sorted = data.windows(2).all(|w| w[0] <= w[1]);
assert!(sorted);

It works, but you have to remember windows(2), get the comparison direction right, and hope the next reader recognizes the pattern.

Now there’s a method that does exactly this:

1
2
3
4
5
let data = vec![1, 3, 5, 7, 9];
assert!(data.is_sorted());

let messy = vec![1, 9, 3, 7, 5];
assert!(!messy.is_sorted());

Empty slices and single-element slices are considered sorted — no edge-case surprises:

1
2
3
let empty: Vec<i32> = vec![];
assert!(empty.is_sorted());
assert!(vec![42].is_sorted());

Need a custom comparator? is_sorted_by takes a closure over pairs of references and returns bool:

1
2
3
4
// Check if sorted by absolute value
let vals: Vec<i32> = vec![-1, 2, -3, 4];
let sorted_by_abs = vals.is_sorted_by(|a, b| a.abs() <= b.abs());
assert!(sorted_by_abs);

And is_sorted_by_key extracts a key first — perfect for structs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct Task {
    priority: u8,
    name: &'static str,
}

let tasks = vec![
    Task { priority: 1, name: "urgent" },
    Task { priority: 3, name: "normal" },
    Task { priority: 5, name: "backlog" },
];

assert!(tasks.is_sorted_by_key(|t| t.priority));

A practical use: skip sorting when the data is already ordered:

1
2
3
4
5
let mut data = vec![1, 2, 3, 4, 5];
if !data.is_sorted() {
    data.sort();
}
// Avoids the O(n log n) sort when data is already O(n)-verified sorted

Available on slices and by extension on Vec, arrays, and anything that derefs to [T]. Stabilized in Rust 1.82 — no crate needed.

73. u64::midpoint — Average Two Numbers Without Overflow

Computing the average of two integers sounds trivial — until it overflows. The midpoint method gives you a correct result every time, no wider types required.

The classic binary search bug lurks in this innocent-looking line:

1
let mid = (low + high) / 2;

When low and high are both large, the addition wraps around and you get garbage. This has bitten production code in every language for decades.

The textbook workaround avoids the addition entirely:

1
let mid = low + (high - low) / 2;

This works — but only when low <= high, and it’s one more thing to get wrong under pressure.

Rust’s midpoint method handles all of this for you:

1
2
3
4
5
6
7
8
9
let a: u64 = u64::MAX - 1;
let b: u64 = u64::MAX;

// This would panic in debug or wrap in release:
// let avg = (a + b) / 2;

// Safe and correct:
let avg = a.midpoint(b);
assert_eq!(avg, u64::MAX - 1);

It works on signed integers too, rounding toward zero:

1
2
3
let x: i32 = -3;
let y: i32 = 4;
assert_eq!(x.midpoint(y), 0);  // rounds toward zero, not -∞

And on floats, where it’s computed without intermediate overflow:

1
2
3
let a: f64 = f64::MAX;
let b: f64 = f64::MAX;
assert_eq!(a.midpoint(b), f64::MAX);  // not infinity

Here’s a binary search that actually works for the full u64 range:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
fn binary_search(sorted: &[i32], target: i32) -> Option<usize> {
    let mut low: usize = 0;
    let mut high: usize = sorted.len();
    while low < high {
        let mid = low.midpoint(high);
        match sorted[mid].cmp(&target) {
            std::cmp::Ordering::Less => low = mid + 1,
            std::cmp::Ordering::Greater => high = mid,
            std::cmp::Ordering::Equal => return Some(mid),
        }
    }
    None
}

let data = vec![1, 3, 5, 7, 9, 11];
assert_eq!(binary_search(&data, 7), Some(3));
assert_eq!(binary_search(&data, 4), None);

Available on all integer types (u8 through u128, i8 through i128, usize, isize) and floats (f32, f64). No crate needed — it’s in the standard library.

72. if let Guards — Pattern Match Inside Match Guards

Match guards are great for adding conditions to arms, but until now you couldn’t destructure inside them. Rust 1.95 stabilizes if let guards, letting you pattern-match right in the guard position.

Suppose you’re matching commands and need to parse a value from one of the variants. Before if let guards, you had to nest an if let inside the arm body:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
enum Command {
    Set(String, String),
    Get(String),
    Quit,
}

fn execute(cmd: Command) -> String {
    match cmd {
        Command::Set(key, value) => {
            if let Ok(n) = value.parse::<i32>() {
                format!("SET {key} = {n} (as integer)")
            } else {
                format!("SET {key} = {value} (as string)")
            }
        }
        Command::Get(key) => format!("GET {key}"),
        Command::Quit => "BYE".to_string(),
    }
}

The Set arm does two different things depending on whether the value parses as an integer — but that logic is buried inside a nested if let. With if let guards, the condition moves into the guard itself:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn execute_v2(cmd: Command) -> String {
    match cmd {
        Command::Set(key, value) if let Ok(n) = value.parse::<i32>() => {
            format!("SET {key} = {n} (as integer)")
        }
        Command::Set(key, value) => {
            format!("SET {key} = {value} (as string)")
        }
        Command::Get(key) => format!("GET {key}"),
        Command::Quit => "BYE".to_string(),
    }
}

Now each arm handles exactly one case. The guard destructures the parse result, and n is available in the arm body. If the guard fails, Rust falls through to the next Set arm — no nested if/else needed.

You can combine if let guards with regular boolean conditions using &&:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn execute_v3(cmd: Command) -> String {
    match cmd {
        Command::Set(key, value)
            if let Ok(n) = value.parse::<i32>()
            && n > 0 =>
        {
            format!("SET {key} = {n} (positive integer)")
        }
        Command::Set(key, value) => {
            format!("SET {key} = {value}")
        }
        Command::Get(key) => format!("GET {key}"),
        Command::Quit => "BYE".to_string(),
    }
}

This feature lands stable in Rust 1.95 (April 2026). If you’ve been nesting if let inside match arms, this is your cleanup opportunity.

71. Inline const Blocks — Compile-Time Evaluation Anywhere

Need a compile-time value in the middle of runtime code? Wrap it in const { } and the compiler evaluates it on the spot — no separate const item needed.

The old way

When you needed a compile-time constant inside a function, you had to hoist it into a separate const item:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn describe_limit() -> &'static str {
    const MAX: usize = 2_usize.pow(16);
    if MAX > 50_000 {
        "high"
    } else {
        "low"
    }
}

fn main() {
    assert_eq!(describe_limit(), "high");
}

It works, but the const declaration is noisy — especially when you only use the value once and it clutters the function body.

Enter const { }

Since Rust 1.79, you can write const { expr } anywhere an expression is expected. The compiler evaluates it at compile time and inlines the result:

1
2
3
4
fn main() {
    let limit = const { 2_usize.pow(16) };
    assert_eq!(limit, 65_536);
}

No named constant, no separate item — just an inline compile-time expression right where you need it.

Generic compile-time values

const { } really shines inside generic functions, where it can compute values based on type parameters:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn make_mask<const N: usize>() -> u128 {
    const { assert!(N <= 128, "mask too wide") };
    if N == 128 { u128::MAX } else { (1u128 << N) - 1 }
}

fn main() {
    assert_eq!(make_mask::<8>(), 0xFF);
    assert_eq!(make_mask::<16>(), 0xFFFF);
    assert_eq!(make_mask::<1>(), 1);
}

The const { assert!(...) } fires at compile time for each monomorphization — if someone writes make_mask::<200>(), they get a compile error, not a runtime panic.

Compile-time assertions

Use const { } to embed compile-time checks directly in your code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn process_buffer<const N: usize>(buf: [u8; N]) -> u8 {
    const { assert!(N <= 1024, "buffer too large") };
    buf[0]
}

fn main() {
    let small = process_buffer([1, 2, 3]);
    assert_eq!(small, 1);

    // This would fail at compile time:
    // let huge = process_buffer([0u8; 2048]);
}

The assertion runs at compile time — if it fails, you get a compile error, not a runtime panic. It’s like a lightweight static_assert from C++, but it works anywhere.

Building lookup tables

const { } shines when you need a precomputed table without polluting the outer scope:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn is_vowel(c: char) -> bool {
    const { ['a', 'e', 'i', 'o', 'u'] }.contains(&c)
}

fn main() {
    assert!(is_vowel('a'));
    assert!(is_vowel('u'));
    assert!(!is_vowel('b'));
    assert!(!is_vowel('z'));
}

The array is built at compile time and the contains check runs at runtime — clean, fast, and self-contained.

Next time you’re about to write const TEMP: ... = ...; just to use it once, reach for const { } instead. It keeps the value where it belongs — right at the point of use.

70. Iterator::intersperse — Join Elements Without Collecting First

Tired of collecting into a Vec just to call .join(",")? intersperse inserts a separator between every pair of elements — lazily, right inside the iterator chain.

The problem

You have an iterator of strings and want to join them with a separator. The classic approach forces you to collect first:

1
2
3
4
5
6
7
8
fn main() {
    let words = vec!["hello", "world", "from", "rust"];

    // Works, but allocates an intermediate Vec<&str> just to join it
    let sentence = words.iter().copied().collect::<Vec<_>>().join(" ");

    assert_eq!(sentence, "hello world from rust");
}

It gets the job done, but that intermediate Vec allocation is wasteful — you’re collecting just to immediately consume it again.

The clean way

intersperse inserts a separator value between every adjacent pair of elements, returning a new iterator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let words = vec!["hello", "world", "from", "rust"];

    let sentence: String = words
        .iter()
        .copied()
        .intersperse(" ")
        .collect();

    assert_eq!(sentence, "hello world from rust");
}

No intermediate Vec. The separator is lazily inserted as you iterate, and collect builds the final String directly.

It works with any type

intersperse isn’t just for strings — it works with any iterator where the element type implements Clone:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let numbers = vec![1, 2, 3, 4];

    let with_zeros: Vec<i32> = numbers
        .iter()
        .copied()
        .intersperse(0)
        .collect();

    assert_eq!(with_zeros, vec![1, 0, 2, 0, 3, 0, 4]);
}

This is handy for building sequences with delimiters, padding, or sentinel values between real data.

When the separator is expensive to create

If your separator is costly to clone, use intersperse_with — it takes a closure that produces the separator on demand:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn main() {
    let parts = vec!["one", "two", "three"];

    let result: String = parts
        .iter()
        .copied()
        .intersperse_with(|| " | ")
        .collect();

    assert_eq!(result, "one | two | three");
}

The closure is only called when a separator is actually needed, so you pay zero cost for single-element or empty iterators.

Edge cases

intersperse handles the corners gracefully — empty iterators stay empty, and single-element iterators pass through unchanged:

1
2
3
4
5
6
7
8
9
fn main() {
    let empty: Vec<&str> = Vec::new();
    let result: String = empty.iter().copied().intersperse(", ").collect();
    assert_eq!(result, "");

    let single = vec!["alone"];
    let result: String = single.iter().copied().intersperse(", ").collect();
    assert_eq!(result, "alone");
}

Next time you reach for .collect::<Vec<_>>().join(...), try intersperse instead — it’s one less allocation and reads just as clearly.

69. Iterator::try_fold — Fold That Knows When to Stop

Need to accumulate values from an iterator but bail on the first error? try_fold gives you the power of fold with the early-exit behavior of ?.

The problem

You’re parsing a list of strings into numbers and summing them. With fold, you have no clean way to short-circuit on a parse failure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
fn main() {
    let inputs = vec!["10", "20", "oops", "40"];

    // This doesn't compile — fold expects the same type every iteration
    // and there's no way to bail early
    let mut total = 0i64;
    let mut error = None;
    for s in &inputs {
        match s.parse::<i64>() {
            Ok(n) => total += n,
            Err(e) => {
                error = Some(e);
                break;
            }
        }
    }

    assert!(error.is_some());
    assert_eq!(total, 30); // partial sum before the error
}

It works, but you’ve traded iterator chains for mutable state and a manual loop.

The clean way

try_fold takes an initial accumulator and a closure that returns Result<Acc, E> (or any type implementing Try). It stops at the first Err and returns it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn sum_parsed(inputs: &[&str]) -> Result<i64, std::num::ParseIntError> {
    inputs.iter().try_fold(0i64, |acc, s| {
        let n = s.parse::<i64>()?;
        Ok(acc + n)
    })
}

fn main() {
    // All valid — returns the sum
    assert_eq!(sum_parsed(&["10", "20", "30"]), Ok(60));

    // Stops at "oops", never touches "40"
    assert!(sum_parsed(&["10", "20", "oops", "40"]).is_err());
}

No mutable variables, no manual loop, no tracking partial state. The ? inside the closure does exactly what you’d expect.

It works with Option too

try_fold works with any Try type. With Option, it stops at the first None:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let values = vec![Some(1), Some(2), Some(3)];
    let sum = values.iter().try_fold(0, |acc, opt| {
        opt.map(|n| acc + n)
    });
    assert_eq!(sum, Some(6));

    let values = vec![Some(1), None, Some(3)];
    let sum = values.iter().try_fold(0, |acc, opt| {
        opt.map(|n| acc + n)
    });
    assert_eq!(sum, None); // stopped at None
}

Bonus: try_for_each

If you don’t need an accumulator and just want to run a fallible operation on each element, try_for_each is the shorthand:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn validate_all(inputs: &[&str]) -> Result<(), std::num::ParseIntError> {
    inputs.iter().try_for_each(|s| {
        s.parse::<i64>()?;
        Ok(())
    })
}

fn main() {
    assert!(validate_all(&["1", "2", "3"]).is_ok());
    assert!(validate_all(&["1", "nope", "3"]).is_err());
}

Both methods are lazy — they only consume as many elements as needed. When your fold can fail, reach for try_fold instead of a manual loop.

#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.