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.

#033 Mar 2026

33. std::mem::take

Ever tried to move a value out of a &mut reference? The borrow checker won’t let you — but std::mem::take will. It swaps the value out and leaves Default::default() in its place.

1
2
3
4
5
6
7
use std::mem;

let mut name = String::from("Ferris");
let taken = mem::take(&mut name);

assert_eq!(taken, "Ferris");
assert_eq!(name, ""); // left with String::default()

This is especially useful when working with enum state machines where you need to consume the current state:

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

enum State {
    Running(String),
    Stopped,
}

impl Default for State {
    fn default() -> Self { State::Stopped }
}

fn reset(state: &mut State) -> Option<String> {
    match mem::take(state) {
        State::Running(data) => Some(data),
        State::Stopped => None,
    }
}

Without mem::take, you’d need .clone() or unsafe gymnastics to get the value out. See also mem::replace for when you want to specify what to leave behind instead of using Default.

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

31. HashMap's entry API

Want to insert a value into a HashMap only if the key doesn’t exist yet? Skip the double lookup — use the entry API.

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

let mut scores: HashMap<&str, Vec<u32>> = HashMap::new();

// Instead of checking .contains_key() then inserting:
scores.entry("alice")
    .or_insert_with(Vec::new)
    .push(100);

scores.entry("alice")
    .or_insert_with(Vec::new)
    .push(200);

assert_eq!(scores["alice"], vec![100, 200]);

The entry API returns an Entry enum — either Occupied or Vacant. The convenience methods make common patterns a one-liner:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::collections::HashMap;

let mut word_count: HashMap<&str, usize> = HashMap::new();
let words = ["hello", "world", "hello", "rust", "hello"];

for word in words {
    *word_count.entry(word).or_insert(0) += 1;
}

// hello => 3, world => 1, rust => 1

or_insert(val) inserts a default, or_insert_with(|| val) lazily computes it, and or_default() uses the type’s Default. All three return a mutable reference to the value, so you can update in place.

#030 Mar 2026

30. dbg! macro

Still using println! for quick debugging? Try dbg! instead — it prints the expression, its value, and the file/line number to stderr. And it returns the value, so you can wrap it around anything.

1
2
3
4
5
6
let a = 2;
let b = dbg!(a * 2) + 1; // prints: [src/main.rs:3] a * 2 = 4
assert_eq!(b, 5);

// works with multiple values too
dbg!(a, b, a + b); // prints each as a tuple

Unlike println!, dbg! takes ownership (or copies for Copy types). If you need to keep the value, pass a reference:

1
2
3
let name = String::from("Ferris");
dbg!(&name); // borrows, doesn't move
println!("{name}"); // still works!

Bonus: dbg! works the same in release builds, and outputs to stderr so it won’t pollute your stdout.

29. Let chains

Tired of deeply nested if let blocks? Rust 2024 edition brings let chains — chain multiple let patterns with && in a single if expression.

1
2
3
4
5
6
7
8
// Before: nested and hard to read
if let Some(a) = opt_a {
    if let Some(b) = opt_b {
        if a > 0 {
            println!("{a} + {b} = {}", a + b);
        }
    }
}

With let chains, flatten the whole thing:

1
2
3
4
5
6
if let Some(a) = opt_a
    && let Some(b) = opt_b
    && a > 0
{
    println!("{a} + {b} = {}", a + b);
}

Works with while too!

1
2
3
4
5
6
7
8
9
let mut iter = vec![Some(1), Some(2), None, Some(4)].into_iter();

while let Some(inner) = iter.next()
    && let Some(val) = inner
{
    println!("got: {val}");
}
// prints: got: 1, got: 2
// stops at None — the inner let fails

You can mix boolean expressions and let bindings freely. Each && can be either a regular condition or a pattern match.

Note: requires edition 2024 (edition = "2024" in Cargo.toml).

#028 Feb 2023

28. Raw string

Curious to know how you can add a double quote in raw strings?

No escape sequences are recognized in raw strings, so adding a backslash does not work.

Use ### to mark the start and the end of a raw string.

1
2
3
4
5
6
7
8
println!("This is a double quote \" ");

// println!(r"This is a double quote " "); // Nope!
// println!(r"This is a double quote \" "); // Nope!

// And this works
println!(r#"This is a double quote " "#);
println!(r###"This is a double quote " "###);
#027 Jan 2023

27. Option's sum/product

Rust’s option implements Sum and Product traits too!

Use it when you want to get None if there is a None element and sum of values otherwise.

1
2
3
let nums: [Option<u32>;3] = [Some(1), Some(10), None];
let maybe_nums: Option<u32> = nums.into_iter().sum();
assert_eq!(maybe_nums, None);

or sum of the values …

1
2
3
let nums = [Some(1), Some(10), Some(100)];
let maybe_nums: Option<u32> = nums.into_iter().sum();
assert_eq!(maybe_nums, Some(111));

And product.

1
2
3
let nums = [Some(1), Some(10), Some(100)];
let maybe_nums: Option<u32> = nums.into_iter().product();
assert_eq!(maybe_nums, Some(1000));
#026 Jan 2023

26. Collecting into Option

Rust’s option implements FromIterator too!

Use it when you want to get None if there is a None element and values otherwise.

1
2
3
let cars = [Some("Porsche"), Some("Ferrari"), None];
let maybe_cars: Option<Vec<_>> = cars.into_iter().collect();
assert_eq!(maybe_cars, None);

or values …

1
2
3
let cars = [Some("Porsche"), Some("Ferrari"), Some("Skoda")];
let maybe_cars: Option<Vec<_>> = cars.into_iter().collect();
assert_eq!(maybe_cars, Some(vec!["Porsche", "Ferrari", "Skoda"]));