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.

#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));