Std

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

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.

#057 Apr 2026

57. New Math Constants — GOLDEN_RATIO and EULER_GAMMA in std

Tired of defining your own golden ratio or Euler-Mascheroni constant? As of Rust 1.94, std ships them out of the box — no more copy-pasting magic numbers.

Before: Roll Your Own

If you needed the golden ratio or the Euler-Mascheroni constant before Rust 1.94, you had to define them yourself:

1
2
const PHI: f64 = 1.618033988749895;
const EULER_GAMMA: f64 = 0.5772156649015329;

This works, but it’s error-prone. One wrong digit and your calculations drift. And every project that needs these ends up with its own slightly-different copy.

After: Just Use std

Rust 1.94 added GOLDEN_RATIO and EULER_GAMMA to the standard consts modules for both f32 and f64:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::f64::consts::{GOLDEN_RATIO, EULER_GAMMA};

fn main() {
    // Golden ratio: (1 + √5) / 2
    let phi = GOLDEN_RATIO;
    assert!((phi * phi - phi - 1.0).abs() < 1e-10);

    // Euler-Mascheroni constant
    let gamma = EULER_GAMMA;
    assert!((gamma - 0.5772156649015329).abs() < 1e-10);

    println!("φ = {phi}");
    println!("γ = {gamma}");
}

These sit right alongside the constants you already know — PI, TAU, E, SQRT_2, and friends.

Where You’d Actually Use Them

Golden ratio shows up in algorithm design (Fibonacci heaps, golden-section search), generative art, and UI layout proportions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::f64::consts::GOLDEN_RATIO;

fn golden_section_dimensions(width: f64) -> (f64, f64) {
    let height = width / GOLDEN_RATIO;
    (width, height)
}

fn main() {
    let (w, h) = golden_section_dimensions(800.0);
    assert!((w / h - GOLDEN_RATIO).abs() < 1e-10);
    println!("Width: {w}, Height: {h:.2}");
}

Euler-Mascheroni constant appears in number theory, harmonic series approximations, and probability:

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

/// Approximate the N-th harmonic number using the
/// asymptotic expansion: H_n ≈ ln(n) + γ + 1/(2n)
fn harmonic_approx(n: u64) -> f64 {
    let nf = n as f64;
    nf.ln() + EULER_GAMMA + 1.0 / (2.0 * nf)
}

fn main() {
    // Exact H_10 = 1 + 1/2 + 1/3 + ... + 1/10
    let exact: f64 = (1..=10).map(|i| 1.0 / i as f64).sum();
    let approx = harmonic_approx(10);

    println!("Exact H_10:  {exact:.6}");
    println!("Approx H_10: {approx:.6}");
    assert!((exact - approx).abs() < 0.01);
}

The Full Lineup

With these additions, std::f64::consts now includes: PI, TAU, E, SQRT_2, SQRT_3, LN_2, LN_10, LOG2_E, LOG2_10, LOG10_2, LOG10_E, FRAC_1_PI, FRAC_1_SQRT_2, FRAC_1_SQRT_2PI, FRAC_2_PI, FRAC_2_SQRT_PI, FRAC_PI_2, FRAC_PI_3, FRAC_PI_4, FRAC_PI_6, FRAC_PI_8, GOLDEN_RATIO, and EULER_GAMMA. That’s a pretty complete toolkit for numerical work — all with full f64 precision, available at compile time.

#055 Apr 2026

55. floor_char_boundary — Truncate Strings Without Breaking UTF-8

Ever tried to truncate a string to a byte limit and got a panic because you sliced in the middle of a multi-byte character? floor_char_boundary fixes that.

The Problem

Slicing a string at an arbitrary byte index panics if that index lands inside a multi-byte UTF-8 character:

1
2
3
4
5
6
let s = "Héllo 🦀 world";
// This panics at runtime!
// let truncated = &s[..5]; // 'é' spans bytes 1..3, index 5 is fine here
// but what if we don't know the content?
let s = "🦀🦀🦀"; // each crab is 4 bytes
// &s[..5] would panic — byte 5 is inside the second crab!

You could scan backward byte-by-byte checking is_char_boundary(), but that’s tedious and easy to get wrong.

The Fix: floor_char_boundary

str::floor_char_boundary(index) returns the largest byte position at or before index that sits on a valid character boundary. Its counterpart ceil_char_boundary gives you the smallest position at or after the index.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn main() {
    let s = "🦀🦀🦀"; // each 🦀 is 4 bytes, total 12 bytes

    // We want ~6 bytes, but byte 6 is inside the second crab
    let i = s.floor_char_boundary(6);
    assert_eq!(i, 4); // rounds down to end of first 🦀
    assert_eq!(&s[..i], "🦀");

    // ceil_char_boundary rounds up instead
    let j = s.ceil_char_boundary(6);
    assert_eq!(j, 8); // rounds up to end of second 🦀
    assert_eq!(&s[..j], "🦀🦀");
}

Real-World Use: Safe Truncation

Here’s a practical helper that truncates a string to fit a byte budget, adding an ellipsis if it was shortened:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
fn truncate(s: &str, max_bytes: usize) -> String {
    if s.len() <= max_bytes {
        return s.to_string();
    }
    let end = s.floor_char_boundary(max_bytes.saturating_sub(3));
    format!("{}...", &s[..end])
}

fn main() {
    let bio = "I love Rust 🦀 and crabs!";
    let short = truncate(bio, 16);
    assert_eq!(short, "I love Rust 🦀...");
    // 'I love Rust 🦀' = 15 bytes + '...' = 18 total
    // Safe! No panics, no broken characters.

    // Short strings pass through unchanged
    assert_eq!(truncate("hi", 10), "hi");
}

No more manual boundary scanning — these two methods handle the UTF-8 dance for you.

54. Cell::update — Modify Interior Values Without the Gymnastics

Tired of writing cell.set(cell.get() + 1) every time you want to tweak a Cell value? Rust 1.88 added Cell::update — one call to read, transform, and write back.

The old way

Cell<T> gives you interior mutability for Copy types, but updating a value always felt clunky:

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

fn main() {
    let counter = Cell::new(0u32);

    // Read, modify, write back — three steps for one logical operation
    counter.set(counter.get() + 1);
    counter.set(counter.get() + 1);
    counter.set(counter.get() + 1);

    assert_eq!(counter.get(), 3);
    println!("Counter: {}", counter.get());
}

You’re calling .get() and .set() in the same expression, which is repetitive and visually noisy — especially when the transformation is more complex than + 1.

Enter Cell::update

Stabilized in Rust 1.88, update takes a closure that receives the current value and returns the new one:

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

fn main() {
    let counter = Cell::new(0u32);

    counter.update(|n| n + 1);
    counter.update(|n| n + 1);
    counter.update(|n| n + 1);

    assert_eq!(counter.get(), 3);
    println!("Counter: {}", counter.get());
}

One call. No repetition of the cell name. The intent — “increment this value” — is immediately clear.

Beyond simple increments

update shines when the transformation is more involved:

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

fn main() {
    let flags = Cell::new(0b0000_1010u8);

    // Toggle bit 0
    flags.update(|f| f ^ 0b0000_0001);
    assert_eq!(flags.get(), 0b0000_1011);

    // Clear the top nibble
    flags.update(|f| f & 0b0000_1111);
    assert_eq!(flags.get(), 0b0000_1011);

    // Saturating shift left
    flags.update(|f| f.saturating_mul(2));
    assert_eq!(flags.get(), 22);

    println!("Flags: {:#010b}", flags.get());
}

Compare that to flags.set(flags.get() ^ 0b0000_0001) — the update version reads like a pipeline of transformations.

A practical example: tracking state in callbacks

Cell::update is especially handy inside closures where you need shared mutable state without reaching for RefCell:

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

fn main() {
    let total = Cell::new(0i64);

    let prices = [199, 450, 85, 320, 1200];
    let discounted: Vec<i64> = prices.iter().map(|&price| {
        let final_price = if price > 500 { price * 9 / 10 } else { price };
        total.update(|t| t + final_price);
        final_price
    }).collect();

    assert_eq!(discounted, vec![199, 450, 85, 320, 1080]);
    assert_eq!(total.get(), 2134);
    println!("Prices: {:?}, Total: {}", discounted, total.get());
}

No RefCell, no runtime borrow checks, no panics — just a clean in-place update.

The signature

1
2
3
impl<T: Copy> Cell<T> {
    pub fn update(&self, f: impl FnOnce(T) -> T);
}

Note the T: Copy bound — this works because Cell copies the value out, passes it to your closure, and copies the result back in. If you need this for non-Copy types, you’ll still want RefCell.

Simple, ergonomic, and long overdue. Available since Rust 1.88.0.

#053 Mar 2026

53. element_offset — Find an Element's Index by Reference

Ever had a reference to an element inside a slice but needed its index? Before Rust 1.94, you’d reach for .position() with value equality or resort to pointer math. Now there’s a cleaner way.

The problem

Imagine you’re scanning a slice and a helper function hands you back a reference to the element it found. You know the reference points somewhere inside your slice, but you need the index — not a value-based search.

1
2
3
fn first_long_word<'a>(words: &'a [&str]) -> Option<&'a &'a str> {
    words.iter().find(|w| w.len() > 5)
}

You could call .position() with value comparison, but that re-scans the slice and compares by value — which is wasteful when you already hold the exact reference.

The solution: element_offset

<[T]>::element_offset takes a reference to an element and returns its Option<usize> index by comparing pointers, not values. If the reference points into the slice, you get Some(index). If it doesn’t, you get None.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
fn main() {
    let words = ["hi", "hello", "rustacean", "world"];

    // A helper hands us a reference into the slice
    let found: &&str = words.iter().find(|w| w.len() > 5).unwrap();

    // Get the index by reference identity — no value scan needed
    let index = words.element_offset(found).unwrap();

    assert_eq!(index, 2);
    assert_eq!(words[index], "rustacean");

    println!("Found '{}' at index {}", found, index);
}

Why not .position()?

.position() compares by value and has to walk the slice from the start. element_offset is an O(1) pointer comparison — it checks whether your reference falls within the slice’s memory range and computes the offset directly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    let values = [10, 20, 10, 30];

    let third = &values[2]; // points at the second '10'

    // position() finds the FIRST 10 (index 0) — wrong!
    let by_value = values.iter().position(|v| v == third);
    assert_eq!(by_value, Some(0));

    // element_offset() finds THIS exact element (index 2) — correct!
    let by_ref = values.element_offset(third);
    assert_eq!(by_ref, Some(2));

    println!("By value: {:?}, By reference: {:?}", by_value, by_ref);
}

This distinction matters whenever your slice has duplicate values.

When the reference is outside the slice

If the reference doesn’t point into the slice, you get None:

1
2
3
4
5
6
7
8
fn main() {
    let a = [1, 2, 3];
    let outside = &42;

    assert_eq!(a.element_offset(outside), None);

    println!("Outside reference: {:?}", a.element_offset(outside));
}

Clean, safe, and no unsafe pointer arithmetic required. Available since Rust 1.94.0.

51. File::lock — File Locking in the Standard Library

Multiple processes writing to the same file? That’s a recipe for corruption. Since Rust 1.89, File::lock gives you OS-backed file locking without external crates.

The problem

You have a CLI tool that appends to a shared log file. Two instances run at the same time, and suddenly your log entries are garbled — half a line from one process interleaved with another. Before 1.89, you’d reach for the fslock or file-lock crate. Now it’s built in.

Exclusive locking

File::lock() acquires an exclusive (write) lock. Only one handle can hold an exclusive lock at a time — all other attempts block until the lock is released:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::fs::File;
use std::io::{self, Write};

fn main() -> io::Result<()> {
    let mut file = File::options()
        .write(true)
        .create(true)
        .open("/tmp/rustbites_lock_demo.txt")?;

    // Blocks until the lock is acquired
    file.lock()?;

    writeln!(file, "safe write from process {}", std::process::id())?;

    // Lock is released when the file is closed (dropped)
    Ok(())
}

When the File is dropped, the lock is automatically released. No manual unlock() needed — though you can call file.unlock() explicitly if you want to release it early.

Shared (read) locking

Sometimes you want to allow multiple readers but block writers. That’s what lock_shared() is for:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::fs::File;
use std::io::{self, Read};

fn main() -> io::Result<()> {
    let mut file = File::open("/tmp/rustbites_lock_demo.txt")?;

    // Multiple processes can hold a shared lock simultaneously
    file.lock_shared()?;

    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    println!("Read: {contents}");

    file.unlock()?; // explicit release
    Ok(())
}

Shared locks coexist with other shared locks, but block exclusive lock attempts. Classic reader-writer pattern, enforced at the OS level.

Non-blocking with try_lock

Don’t want to wait? try_lock() and try_lock_shared() return immediately instead of blocking:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::fs::{self, File, TryLockError};

fn main() -> std::io::Result<()> {
    let file = File::options()
        .write(true)
        .create(true)
        .open("/tmp/rustbites_trylock.txt")?;

    match file.try_lock() {
        Ok(()) => println!("Lock acquired!"),
        Err(TryLockError::WouldBlock) => println!("File is busy, try later"),
        Err(TryLockError::Error(e)) => return Err(e),
    }

    Ok(())
}

If another process holds the lock, you get TryLockError::WouldBlock instead of hanging. Perfect for tools that should fail fast rather than block when another instance is already running.

Key details

  • Advisory locks: these locks are advisory on most platforms — they don’t prevent other processes from reading/writing the file unless those processes also use locking
  • Automatic release: locks are released when the File handle is dropped
  • Cross-platform: works on Linux, macOS, and Windows (uses flock on Unix, LockFileEx on Windows)
  • Stable since Rust 1.89
#049 Mar 2026

49. std::io::pipe — Anonymous Pipes in the Standard Library

Need to wire up stdout and stderr from a child process, or stream data between threads? Since Rust 1.87, std::io::pipe() gives you OS-backed anonymous pipes without reaching for external crates.

What’s an anonymous pipe?

A pipe is a one-way data channel: one end writes, the other reads. Before 1.87, you needed the os_pipe crate or platform-specific code to get one. Now it’s a single function call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
use std::io::{self, Read, Write};

fn main() -> io::Result<()> {
    let (mut reader, mut writer) = io::pipe()?;

    writer.write_all(b"hello from the pipe")?;
    drop(writer); // close the write end so reads hit EOF

    let mut buf = String::new();
    reader.read_to_string(&mut buf)?;
    assert_eq!(buf, "hello from the pipe");

    println!("Received: {buf}");
    Ok(())
}

pipe() returns a (PipeReader, PipeWriter) pair. PipeReader implements Read, PipeWriter implements Write — they plug into any generic I/O code you already have.

Merge stdout and stderr from a child process

The killer use case: capture both output streams from a subprocess as a single interleaved stream:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
use std::io::{self, Read};
use std::process::Command;

fn main() -> io::Result<()> {
    let (mut recv, send) = io::pipe()?;

    let mut child = Command::new("echo")
        .arg("hello world")
        .stdout(send.try_clone()?)
        .stderr(send)
        .spawn()?;

    child.wait()?;

    let mut output = String::new();
    recv.read_to_string(&mut output)?;
    assert!(output.contains("hello world"));

    println!("Combined output: {output}");
    Ok(())
}

The try_clone() on the writer lets both stdout and stderr write to the same pipe. When both copies of the PipeWriter are dropped (one moved into stdout, one into stderr), reads on the PipeReader return EOF.

Why not just use Command::output()?

Command::output() captures stdout and stderr separately into Vec<u8> — you get two blobs, no interleaving, and everything is buffered in memory. With pipes, you can stream the output as it arrives, merge the two streams, or fan data into multiple consumers. Pipes give you the plumbing; output() gives you the convenience.

Key behavior

A read on PipeReader blocks until data is available or all writers are closed. A write on PipeWriter blocks when the OS pipe buffer is full. This is the same behavior as Unix pipes under the hood — because that’s exactly what they are.

#047 Mar 2026

47. Vec::pop_if — Conditionally Pop the Last Element

Need to remove the last element of a Vec only when it meets a condition? Vec::pop_if does exactly that — no index juggling, no separate check-then-pop.

The old way

Before pop_if, you’d write something like this:

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

if stack.last().is_some_and(|x| *x > 3) {
    let val = stack.pop().unwrap();
    println!("Popped: {val}");
}

Two separate calls, and a subtle TOCTOU gap if you’re not careful — last() checks one thing, then pop() acts on an assumption.

Enter pop_if

Stabilized in Rust 1.86, pop_if combines the check and the removal into one atomic operation:

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

// Pop the last element only if it's greater than 3
let popped = stack.pop_if(|x| *x > 3);
assert_eq!(popped, Some(4));
assert_eq!(stack, [1, 2, 3]);

// Now the last element is 3 — doesn't match, so nothing happens
let stayed = stack.pop_if(|x| *x > 3);
assert_eq!(stayed, None);
assert_eq!(stack, [1, 2, 3]);

The closure receives a &mut T reference to the last element. If it returns true, the element is removed and returned as Some(T). If false (or the vec is empty), you get None.

Mutable access inside the predicate

Because the closure gets &mut T, you can even modify the element before deciding whether to pop it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let mut tasks = vec![
    String::from("buy milk"),
    String::from("URGENT: deploy fix"),
];

let urgent = tasks.pop_if(|task| {
    if task.starts_with("URGENT:") {
        *task = task.replacen("URGENT: ", "", 1);
        true
    } else {
        false
    }
});

assert_eq!(urgent.as_deref(), Some("deploy fix"));
assert_eq!(tasks, vec!["buy milk"]);

A practical use: draining from the back

pop_if is handy for processing a sorted vec from the tail. Think of a priority queue backed by a sorted vec where you only want to process items above a threshold:

1
2
3
4
5
6
7
8
9
let mut scores = vec![10, 25, 50, 75, 90];

let mut high_scores = Vec::new();
while let Some(score) = scores.pop_if(|s| *s >= 70) {
    high_scores.push(score);
}

assert_eq!(high_scores, vec![90, 75]);
assert_eq!(scores, vec![10, 25, 50]);

Clean, expressive, and no off-by-one errors. Another small addition to Vec that makes everyday Rust just a bit nicer.

#042 Mar 2026

42. array::from_fn — Build Arrays from a Function

Need a fixed-size array where each element depends on its index? Skip the vec![..].try_into().unwrap() dance — std::array::from_fn builds it in place with zero allocation.

The problem

Rust arrays [T; N] have a fixed size known at compile time, but initializing them with computed values used to be awkward:

1
2
3
4
5
6
// Clunky: build a Vec, then convert
let squares: [u64; 5] = (0..5)
    .map(|i| i * i)
    .collect::<Vec<_>>()
    .try_into()
    .unwrap();

That allocates a Vec on the heap just to get a stack array. For Copy types you could use [0u64; 5] and a loop, but that doesn’t work for non-Copy types and is verbose either way.

The fix: array::from_fn

1
2
let squares: [u64; 5] = std::array::from_fn(|i| (i as u64) * (i as u64));
assert_eq!(squares, [0, 1, 4, 9, 16]);

The closure receives the index (0..N) and returns the element. No heap allocation, no unwrapping — just a clean array on the stack.

Non-Copy types? No problem

from_fn shines when your elements don’t implement Copy:

1
2
3
let labels: [String; 4] = std::array::from_fn(|i| format!("item_{i}"));
assert_eq!(labels[0], "item_0");
assert_eq!(labels[3], "item_3");

Try doing that with [String::new(); 4] — the compiler won’t let you because String isn’t Copy.

Stateful initialization

The closure can capture mutable state. Elements are produced left to right, index 0 first:

1
2
3
4
5
6
7
let mut acc = 1u64;
let powers_of_two: [u64; 6] = std::array::from_fn(|_| {
    let val = acc;
    acc *= 2;
    val
});
assert_eq!(powers_of_two, [1, 2, 4, 8, 16, 32]);

A practical example: lookup tables

Build a compile-time-friendly lookup table for ASCII case conversion offsets:

1
2
3
4
5
6
let is_upper: [bool; 128] = std::array::from_fn(|i| {
    (i as u8).is_ascii_uppercase()
});
assert!(is_upper[b'A' as usize]);
assert!(!is_upper[b'a' as usize]);
assert!(!is_upper[b'0' as usize]);

Why it matters

std::array::from_fn has been stable since Rust 1.63. It avoids heap allocation, works with any type (no Copy or Default bound), and keeps your code readable. Anytime you reach for Vec just to build a fixed-size array — stop, and use from_fn instead.

#032 Mar 2026

32. iter::successors

Need to generate a sequence where each element depends on the previous one? std::iter::successors turns any “next from previous” logic into a lazy iterator.

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

// Powers of 10 that fit in a u32
let powers: Vec<u32> = successors(Some(1u32), |&n| n.checked_mul(10))
    .collect();

// [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000]
assert_eq!(powers.len(), 10);

You give it a starting value and a closure that computes the next element from a reference to the current one. Return None to stop — here checked_mul naturally returns None on overflow, so the iterator terminates on its own.

It works great for any recurrence:

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

// Collatz sequence starting from 12
let collatz: Vec<u64> = successors(Some(12u64), |&n| match n {
    1 => None,
    n if n % 2 == 0 => Some(n / 2),
    n => Some(3 * n + 1),
}).collect();

assert_eq!(collatz, vec![12, 6, 3, 10, 5, 16, 8, 4, 2, 1]);

Think of it as unfold for when your state is the yielded value. Simple, lazy, and zero-allocation until you collect.