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

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