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.

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