Impl-Trait

196. Return impl Iterator, Not Vec — Let the Caller Decide What to Do

Returning a Vec from a helper allocates eagerly, every time — even when the caller only wants the first match or a running sum. Return impl Iterator instead and the allocation simply never happens unless the caller asks for it.

This is the function-boundary version of yesterday’s bite-195: chaining adapters avoids temporary Vecs inside a pipeline; returning impl Iterator avoids forcing one across a function call.

The eager version

A helper that builds and returns a Vec commits to a heap allocation and a full pass over the data before the caller has said what they want:

1
2
3
fn evens_doubled(nums: &[i32]) -> Vec<i32> {
    nums.iter().filter(|&&n| n % 2 == 0).map(|&n| n * 2).collect()
}

If the caller just wants the first result, they still pay for the whole Vec:

1
2
3
let data = [1, 2, 3, 4, 5, 6];
let first = evens_doubled(&data).into_iter().next(); // allocated all 3, used 1
assert_eq!(first, Some(4));

Hand back the iterator instead

Drop the .collect() and return the lazy iterator. The + '_ ties its lifetime to the borrowed slice:

1
2
3
fn evens_doubled(nums: &[i32]) -> impl Iterator<Item = i32> + '_ {
    nums.iter().filter(|&&n| n % 2 == 0).map(|&n| n * 2)
}

Now nothing runs until the caller pulls values through — and they pick the consumer:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let data = [1, 2, 3, 4, 5, 6];

let v: Vec<i32> = evens_doubled(&data).collect(); // collect if you want to
assert_eq!(v, [4, 8, 12]);

let total: i32 = evens_doubled(&data).sum();       // or fold straight to a number
assert_eq!(total, 24);

let first_big = evens_doubled(&data).find(|&n| n > 5); // or short-circuit
assert_eq!(first_big, Some(8));                    // stops at 8, never doubles 6

The find call never allocates and never touches the last element. The Vec-returning version couldn’t do that — collect() always drains the whole thing first.

The one rule: don’t borrow a local

The iterator you return can borrow your parameters, but not data you created inside the function — that data is dropped when the function ends. Iterators over owned values (like a Range) carry no borrow, so they just work:

1
2
3
4
5
6
fn squares(n: u32) -> impl Iterator<Item = u32> {
    (1..=n).map(|x| x * x)
}

let sq: Vec<u32> = squares(4).collect();
assert_eq!(sq, [1, 4, 9, 16]);

If you must produce owned data inside the function and stream it out, move it into the iterator (e.g. vec.into_iter() or a move closure) rather than returning a borrow of a local.

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.