43. Vec::extract_if — Remove Elements and Keep Them

Ever needed to split a Vec into two groups — the ones you keep and the ones you remove? retain discards the removed items. Now there’s a better way.

Vec::extract_if (stable since Rust 1.87) removes elements matching a predicate and hands them back as an iterator — in a single pass.

The old way — two passes, logic must stay in sync

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

// Collect the evens first…
let evens: Vec<i32> = numbers.iter().filter(|&&x| x % 2 == 0).copied().collect();
// …then remove them (predicate must match exactly)
numbers.retain(|&x| x % 2 != 0);

assert_eq!(numbers, [1, 3, 5]);
assert_eq!(evens,   [2, 4, 6]);

The filter and the retain predicates must be inverses of each other — easy to mistype, and you touch the data twice.

The new way — one pass, one predicate

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

let evens: Vec<i32> = numbers.extract_if(.., |&mut x| x % 2 == 0).collect();

assert_eq!(numbers, [1, 3, 5]);
assert_eq!(evens,   [2, 4, 6]);

extract_if walks the Vec, removes every element where the closure returns true, and yields it. The .. is a range — you can narrow it to only consider a slice of the vector.

Real-world example: draining a work queue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#[derive(Debug)]
struct Job { id: u32, priority: u8 }

let mut queue = vec![
    Job { id: 1, priority: 3 },
    Job { id: 2, priority: 9 },
    Job { id: 3, priority: 1 },
    Job { id: 4, priority: 8 },
];

// Pull out all high-priority jobs for immediate processing
let urgent: Vec<Job> = queue.extract_if(.., |j| j.priority >= 8).collect();

assert_eq!(urgent.len(), 2);  // jobs 2 and 4
assert_eq!(queue.len(),  2);  // jobs 1 and 3 remain

HashMap and HashSet also gained extract_if in Rust 1.88.

Note: The closure takes &mut T, so you can even mutate elements mid-extraction before deciding whether to remove them.

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

#041 Mar 2026

41. Async Closures — Pass Async Code Like Any Other Closure

Accepting an async callback used to mean a tangle of Fn(T) -> Fut where Fut: Future. Rust 1.85 stabilizes async closures — write async |x| { ... } and accept them with impl async Fn(T) -> U.

The old workaround

Before Rust 1.85, accepting an async function as a parameter meant spelling out the future type explicitly:

1
2
3
4
5
6
7
async fn run<F, Fut>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> Fut,
    Fut: std::future::Future<Output = i32>,
{
    f(x).await
}

It compiles, but the signature is noisy. It gets worse once you need FnMut, higher-ranked lifetimes, or closures that borrow from their captures.

The new way

1
2
3
4
5
6
7
async fn run(f: impl AsyncFn(i32) -> i32, x: i32) -> i32 {
    f(x).await
}

let double = async |x: i32| x * 2;
let result = run(double, 21).await;
assert_eq!(result, 42);

async |...| { ... } is the syntax for an async closure. Use AsyncFn(T) -> U bounds at call sites — AsyncFn, AsyncFnMut, and AsyncFnOnce mirror the regular Fn family and were stabilized alongside async closures in Rust 1.85.

Capturing state works naturally

Async closures capture their environment exactly like regular closures:

1
2
3
4
let base = 10_i32;
let adder = async |x: i32| base + x;

assert_eq!(adder(32).await, 42);

No extra boxing or lifetime gymnastics — the closure borrows base just as a sync closure would.

Apply it twice

1
2
3
4
5
6
async fn apply_twice(f: impl AsyncFn(i32) -> i32, x: i32) -> i32 {
    f(f(x).await).await
}

let double = async |x: i32| x * 2;
assert_eq!(apply_twice(double, 5).await, 20); // 5 → 10 → 20

Why it matters

The real payoff is in generic async APIs — retry helpers, middleware, event hooks — anywhere you’d pass a callback. Instead of Pin<Box<dyn Future>> boilerplate you get a clean bound:

1
2
3
4
5
6
7
8
async fn retry(op: impl AsyncFn() -> Result<i32, String>, tries: u32) -> Option<i32> {
    for _ in 0..tries {
        if let Ok(val) = op().await {
            return Some(val);
        }
    }
    None
}

Async closures are available in Rust 1.85+ (stable since February 2025). Make sure your crate uses edition = "2021" or later.

40. Scoped Threads — Borrow Across Threads Without Arc

Need to share stack data with spawned threads? std::thread::scope lets you borrow local variables across threads — no Arc, no .clone().

The problem

With std::thread::spawn, you can’t borrow local data because the thread might outlive the data:

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

// This won't compile — `data` might be dropped
// while the thread is still running
// std::thread::spawn(|| {
//     println!("{:?}", data);
// });

The classic workaround is wrapping everything in Arc:

1
2
3
4
5
6
7
8
9
use std::sync::Arc;

let data = Arc::new(vec![1, 2, 3]);
let data_clone = Arc::clone(&data);

let handle = std::thread::spawn(move || {
    println!("{:?}", data_clone);
});
handle.join().unwrap();

It works, but it’s noisy — especially when you just want to read some data in parallel.

The fix: std::thread::scope

Scoped threads guarantee that all spawned threads finish before the scope exits, so borrowing is safe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let data = vec![1, 2, 3];
let mut results = vec![];

std::thread::scope(|s| {
    s.spawn(|| {
        // Borrowing `data` directly — no Arc needed
        println!("Thread sees: {:?}", data);
    });

    s.spawn(|| {
        let sum: i32 = data.iter().sum();
        println!("Sum: {sum}");
    });
});

// All threads have joined here — guaranteed
println!("Done! data is still ours: {:?}", data);

Mutable access works too

Since the scope enforces proper lifetimes, you can even have one thread mutably borrow something:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let mut counts = [0u32; 3];

std::thread::scope(|s| {
    for (i, count) in counts.iter_mut().enumerate() {
        s.spawn(move || {
            *count = (i as u32 + 1) * 10;
        });
    }
});

assert_eq!(counts, [10, 20, 30]);

Each thread gets exclusive access to its own element — the borrow checker is happy, no Mutex required.

When to reach for scoped threads

Use std::thread::scope when you need parallel work on local data and don’t want the overhead or ceremony of Arc/Mutex. It’s perfect for fork-join parallelism: spin up threads, borrow what you need, collect results when they’re done.

39. Trait Upcasting — Cast dyn Trait to dyn Supertrait

Since Rust 1.86, you can upcast a trait object to its supertrait — no workarounds needed.

The problem

Imagine you have a trait hierarchy:

1
2
3
4
5
use std::any::Any;

trait Animal: Any {
    fn name(&self) -> &str;
}

Before Rust 1.86, if you had a &dyn Animal, you couldn’t simply cast it to &dyn Any. You’d have to add an explicit method to your trait:

1
2
3
4
5
// The old workaround — adding a method just to upcast
trait Animal: Any {
    fn name(&self) -> &str;
    fn as_any(&self) -> &dyn Any;
}

This was boilerplate that every trait hierarchy had to carry around.

The fix: trait upcasting

Now you can coerce a trait object directly to any of its supertraits:

 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
26
27
28
29
use std::any::Any;

trait Animal: Any {
    fn name(&self) -> &str;
}

struct Dog;

impl Animal for Dog {
    fn name(&self) -> &str {
        "Rex"
    }
}

fn print_if_dog(animal: &dyn Animal) {
    // Upcast to &dyn Any — just works!
    let any: &dyn Any = animal;

    if let Some(dog) = any.downcast_ref::<Dog>() {
        println!("Good boy, {}!", dog.name());
    } else {
        println!("Not a dog.");
    }
}

fn main() {
    let dog = Dog;
    print_if_dog(&dog);
}

The key line is let any: &dyn Any = animal; — this coercion from &dyn Animal to &dyn Any used to be a compiler error and now just works.

It works with any supertrait chain

Upcasting isn’t limited to Any. It works for any supertrait relationship:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
trait Drawable {
    fn draw(&self);
}

trait Widget: Drawable {
    fn click(&self);
}

fn draw_it(widget: &dyn Widget) {
    // Coerce &dyn Widget → &dyn Drawable
    let drawable: &dyn Drawable = widget;
    drawable.draw();
}

This makes trait object hierarchies much more ergonomic. No more as_drawable() helper methods cluttering your traits.

38. #[must_use] — Never Ignore What Matters

Rust’s #[must_use] attribute turns silent bugs into compile-time warnings — making sure important return values never get accidentally ignored.

The Problem: Silently Ignoring Results

Here’s a classic bug that can haunt any codebase:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
fn remove_expired_tokens(tokens: &mut Vec<String>) -> usize {
    let before = tokens.len();
    tokens.retain(|t| !t.starts_with("exp_"));
    before - tokens.len()
}

fn main() {
    let mut tokens = vec![
        "exp_abc".to_string(),
        "valid_xyz".to_string(),
        "exp_def".to_string(),
    ];

    // Bug: we call the function but ignore the count!
    remove_expired_tokens(&mut tokens);

    // No warning, no error — the return value just vanishes
}

The function works fine, but the caller threw away useful information without even a whisper from the compiler.

The Fix: #[must_use]

Add #[must_use] to the function and the compiler has your back:

1
2
3
4
5
6
#[must_use = "returns the number of removed tokens"]
fn remove_expired_tokens(tokens: &mut Vec<String>) -> usize {
    let before = tokens.len();
    tokens.retain(|t| !t.starts_with("exp_"));
    before - tokens.len()
}

Now if someone calls remove_expired_tokens(&mut tokens); without using the result, the compiler emits:

1
2
3
4
warning: unused return value of `remove_expired_tokens` that must be used
  --> src/main.rs:14:5
   |
   = note: returns the number of removed tokens

Works on Types Too

#[must_use] isn’t just for functions — it shines on types:

1
2
3
4
5
#[must_use = "this Result may contain an error that should be handled"]
enum DatabaseResult<T> {
    Ok(T),
    Err(String),
}

This is exactly why calling .map() on an iterator without collecting produces a warning — Map is marked #[must_use] in std.

Already in the Standard Library

Rust’s standard library uses #[must_use] extensively. Result, Option, MutexGuard, and many iterator adapters are all marked with it. That’s why you get a warning for:

1
vec![1, 2, 3].iter().map(|x| x * 2);  // warning: unused `Map`

The iterator does nothing until consumed — and #[must_use] makes sure you don’t forget.

Quick Rules

Use #[must_use] when:

  • A function returns a Result or error indicator — callers should handle failures
  • A function is pure (no side effects) — ignoring the return means the call was pointless
  • A type is lazy (like iterators) — it does nothing until consumed
  • The return value carries critical information the caller likely needs

The custom message string is optional but highly recommended — it tells the developer why they shouldn’t ignore the value.

37. Option::zip

Need to combine two Option values into a pair? Option::zip merges them into a single Option<(A, B)> — if either is None, you get None back.

The problem

You have two optional values and need both to proceed. The classic approach uses nested matching:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let name: Option<&str> = Some("Alice");
let age: Option<u32> = Some(30);

// Nested match — gets unwieldy fast
let greeting = match name {
    Some(n) => match age {
        Some(a) => Some(format!("{n} is {a} years old")),
        None => None,
    },
    None => None,
};

assert_eq!(greeting, Some("Alice is 30 years old".to_string()));

The fix: Option::zip

Zip collapses two Options into one tuple:

1
2
3
4
5
6
let name: Option<&str> = Some("Alice");
let age: Option<u32> = Some(30);

let greeting = name.zip(age).map(|(n, a)| format!("{n} is {a} years old"));

assert_eq!(greeting, Some("Alice is 30 years old".to_string()));

One line instead of six. If either value is None, zip short-circuits to None:

1
2
3
4
let name: Option<&str> = Some("Alice");
let age: Option<u32> = None;

assert_eq!(name.zip(age), None);

Bonus: zip with and_then

You can chain zip into more complex pipelines:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fn lookup_user(id: u32) -> Option<String> {
    if id == 1 { Some("Alice".to_string()) } else { None }
}

fn lookup_role(id: u32) -> Option<String> {
    if id == 1 { Some("Admin".to_string()) } else { None }
}

let result = lookup_user(1)
    .zip(lookup_role(1))
    .map(|(user, role)| format!("{user} ({role})"));

assert_eq!(result, Some("Alice (Admin)".to_string()));

Option::zip is stable since Rust 1.46 and works anywhere you need both-or-nothing semantics without the nesting.

36. Cow<str> — Clone on Write

Stop cloning strings “just in case” — Cow<str> lets you borrow when you can and clone only when you must.

The problem

You’re writing a function that sometimes needs to modify a string and sometimes doesn’t. The easy fix? Clone every time:

1
2
3
4
5
6
7
fn ensure_greeting(name: &str) -> String {
    if name.starts_with("Hello") {
        name.to_string() // unnecessary clone!
    } else {
        format!("Hello, {name}!")
    }
}

This works, but that first branch allocates a brand-new String even though name is already perfect as-is. In a hot loop, those wasted allocations add up.

Enter Cow<str>

Cow stands for Clone on Write. It holds either a borrowed reference or an owned value, and only clones when you actually need to mutate or take ownership:

1
2
3
4
5
6
7
8
9
use std::borrow::Cow;

fn ensure_greeting(name: &str) -> Cow<str> {
    if name.starts_with("Hello") {
        Cow::Borrowed(name) // zero-cost: just wraps the reference
    } else {
        Cow::Owned(format!("Hello, {name}!"))
    }
}

Now the happy path (name already starts with “Hello”) does zero allocation. The caller gets a Cow<str> that derefs to &str transparently — most code won’t even notice the difference.

Using Cow values

Because Cow<str> implements Deref<Target = str>, you can use it anywhere a &str is expected:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use std::borrow::Cow;

fn ensure_greeting(name: &str) -> Cow<str> {
    if name.starts_with("Hello") {
        Cow::Borrowed(name)
    } else {
        Cow::Owned(format!("Hello, {name}!"))
    }
}

fn main() {
    let greeting = ensure_greeting("Hello, world!");
    assert_eq!(&*greeting, "Hello, world!");

    // Call &str methods directly on Cow
    assert!(greeting.contains("world"));

    // Only clone into String when you truly need ownership
    let _owned: String = greeting.into_owned();

    let greeting2 = ensure_greeting("Rust");
    assert_eq!(&*greeting2, "Hello, Rust!");
}

When to reach for Cow

Cow shines in these situations:

  • Conditional transformations — functions that modify input only sometimes (normalization, trimming, escaping)
  • Config/lookup values — return a static default or a dynamically built string
  • Parser outputs — most tokens are slices of the input, but some need unescaping

The Cow type works with any ToOwned pair, not just strings. You can use Cow<[u8]>, Cow<Path>, or Cow<[T]> the same way.

Quick reference

OperationCost
Cow::Borrowed(s)Free — wraps a reference
Cow::Owned(s)Whatever creating the owned value costs
*cow (deref)Free
cow.into_owned()Free if already owned, clones if borrowed
cow.to_mut()Clones if borrowed, then gives &mut access

35. LazyLock

Still pulling in lazy_static or once_cell just for a lazy global? std::sync::LazyLock does the same thing — zero dependencies.

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

static CONFIG: LazyLock<Vec<String>> = LazyLock::new(|| {
    // Imagine this reads from a file or env
    vec!["debug".to_string(), "verbose".to_string()]
});

fn main() {
    // CONFIG is initialized on first access
    println!("flags: {:?}", *CONFIG);
    assert_eq!(CONFIG.len(), 2);
}

LazyLock was stabilized in Rust 1.80 as the std replacement for once_cell::sync::Lazy and lazy_static!. It initializes the value exactly once on first access, is Sync by default, and works in static items without macros.

For single-threaded or non-static use, there’s also LazyCell — same idea but without the synchronization overhead:

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

fn main() {
    let greeting = LazyCell::new(|| {
        println!("computing...");
        "Hello, Rust!".to_uppercase()
    });

    println!("before access");
    // "computing..." prints here, on first deref
    assert_eq!(*greeting, "HELLO, RUST!");
    // second access — no recomputation
    assert_eq!(*greeting, "HELLO, RUST!");
}

The output is:

1
2
before access
computing...

The closure runs lazily on first Deref, and the result is cached for all subsequent accesses. No unwrap(), no Mutex, no external crates — just clean lazy initialization from std.

34. array_windows

Need to look at consecutive pairs (or triples) in a slice? Stop manually indexing — array_windows gives you fixed-size windows as arrays, not slices.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let temps = [18.0, 21.5, 19.0, 23.0, 22.5];

// Before: manual indexing 😬
for i in 0..temps.len() - 1 {
    let diff = temps[i + 1] - temps[i];
    println!({diff:+.1}");
}

// After: array_windows ✨
for [prev, next] in temps.array_windows() {
    let diff = next - prev;
    println!({diff:+.1}");
}

Stabilized in Rust 1.94, array_windows works like .windows(n) but the window size is a const generic — so you get &[T; N] instead of &[T]. That means you can destructure directly in the pattern.

It’s great for detecting trends, computing deltas, or validating sequences:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let readings = [3, 7, 2, 9, 1, 8];

let all_increasing = readings
    .array_windows()
    .all(|[a, b]| b > a);

assert!(!all_increasing);

// Works with triples too
let has_valley = readings
    .array_windows()
    .any(|[a, b, c]| b < a && b < c);

assert!(has_valley); // 2 is a valley between 7 and 9

No bounds checks, no .try_into().unwrap() dance. Just clean pattern matching on fixed-size windows.