Smart-Pointers

163. Cow::to_mut — Lazy In-Place Mutation Through Cow

Cow<str> is the type everyone reaches for when a function might need to modify its input. Cow::Borrowed and Cow::Owned are the constructors that get the spotlight; to_mut is the third piece, and it’s the one that actually pays off the laziness.

What to_mut does

to_mut takes &mut Cow<str> and hands back &mut String:

  • If the Cow is already Owned, you get a direct &mut to the inner String.
  • If it’s Borrowed, to_mut clones the slice into a fresh String, swaps the Cow over to Owned, and then hands you the mutable reference.

That asymmetry is the whole point. Many callers borrow and never touch to_mut — they never allocate. The ones that do call it pay the allocation cost exactly once, on first write.

A walking-the-string example

Expand \t into two spaces, but only allocate if the input actually contains a tab:

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

fn expand_tabs(s: &str) -> Cow<'_, str> {
    let mut out: Cow<'_, str> = Cow::Borrowed(s);
    if let Some(i) = s.find('\t') {
        // First write — `to_mut` clones the slice into a String, then we
        // rebuild from byte `i` onwards.
        let buf = out.to_mut();
        buf.truncate(i);
        for c in s[i..].chars() {
            if c == '\t' {
                buf.push_str("  ");
            } else {
                buf.push(c);
            }
        }
    }
    out
}

The happy path — input has no tab — never enters the if, never allocates, and returns the original slice wrapped in Cow::Borrowed. The unhappy path allocates exactly once.

Composing transformations

to_mut really earns its keep when you chain several optional mutations. The first one that fires flips the Cow to Owned; every following mutation sees an already-owned buffer and reuses it:

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

fn apply_rules<'a>(s: &'a str, rules: &[(char, &str)]) -> Cow<'a, str> {
    let mut out: Cow<'a, str> = Cow::Borrowed(s);
    for &(from, to) in rules {
        if out.contains(from) {
            let replaced = out.replace(from, to);
            *out.to_mut() = replaced;
        }
    }
    out
}

Three things worth pointing at. First, out.contains(from) works because Cow<str> derefs to str. Second, the assignment *out.to_mut() = replaced replaces the inner String, not the Cow itself. Third, once the first rule fires, all subsequent to_mut calls are a no-op &mut String — no extra clones.

Pitfall: to_mut always commits

There’s no “preview, then maybe commit” mode. Calling to_mut on a borrowed Cow clones immediately, even if you never end up writing through the returned reference. So this is a trap:

1
2
3
4
if !out.is_empty() {
    let _ = out.to_mut();  // allocates even though we may not change anything
    // ... maybe mutate, maybe not
}

Guard the call with the actual condition that means “I’m about to write,” not the condition that means “I might.” The mental shortcut: to_mut is the moment you trade your &str for a String. Reach for it lazily, but commit completely.

#113 May 2026

113. Arc::make_mut — Mutate Inside an Arc Without the Dance

You have an Arc<T>, you want a &mut T. Arc only hands out &T, so the usual workaround is clone-the-inner, mutate, rewrap. Arc::make_mut does that for you — and skips the clone when no one else is watching.

The manual version everyone writes once and then copies forever:

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

let mut shared = Arc::new(vec![1, 2, 3]);

let mut owned: Vec<i32> = (*shared).clone(); // always clones
owned.push(4);
shared = Arc::new(owned);                    // always reallocates the Arc

assert_eq!(*shared, vec![1, 2, 3, 4]);

It works, but it clones the Vec and reallocates the Arc every single time — even when this Arc is the only one pointing at the data.

Arc::make_mut takes &mut Arc<T> and hands you &mut T:

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

let mut shared = Arc::new(vec![1, 2, 3]);

Arc::make_mut(&mut shared).push(4);

assert_eq!(*shared, vec![1, 2, 3, 4]);

One call, one borrow, and — crucially — no clone when this Arc is unique:

1
2
3
4
5
use std::sync::Arc;

let mut solo = Arc::new(vec![1, 2, 3]);
Arc::make_mut(&mut solo).push(99); // strong_count == 1, mutates in place
assert_eq!(*solo, vec![1, 2, 3, 99]);

When the Arc is shared, make_mut quietly clones the inner value into a fresh allocation and detaches your handle from the rest. The other handles keep seeing the old data — clone-on-write, exactly like you’d want:

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

let mut a = Arc::new(vec![1, 2, 3]);
let b = Arc::clone(&a);            // strong_count == 2

Arc::make_mut(&mut a).push(99);    // clones, then mutates the clone

assert_eq!(*a, vec![1, 2, 3, 99]); // a moved to its own allocation
assert_eq!(*b, vec![1, 2, 3]);     // b still sees the original

The same method exists on Rc for single-threaded code, with identical semantics. Reach for make_mut whenever you find yourself cloning the inside of an Arc just to change one field — you’ll skip the allocation in the common case and get an honest &mut T in return.

83. Arc::unwrap_or_clone — Take Ownership Without the Dance

You need to own a T but all you have is an Arc<T>. The old pattern is a six-line fumble with try_unwrap. Arc::unwrap_or_clone collapses it into one call — and skips the clone entirely when it can.

The old dance

Arc::try_unwrap hands you the inner value — but only if you’re the last reference. Otherwise it gives your Arc back, and you have to clone.

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

let arc = Arc::new(String::from("hello"));
let owned: String = match Arc::try_unwrap(arc) {
    Ok(inner) => inner,
    Err(still_shared) => (*still_shared).clone(),
};
assert_eq!(owned, "hello");

Every place that wanted an owned T from an Arc<T> wrote this same pattern, often subtly wrong.

The fix: unwrap_or_clone

Stabilized in Rust 1.76, Arc::unwrap_or_clone does exactly the right thing: move the inner value out if we’re the last owner, clone it otherwise.

1
2
3
4
5
use std::sync::Arc;

let arc = Arc::new(String::from("hello"));
let owned: String = Arc::unwrap_or_clone(arc);
assert_eq!(owned, "hello");

One call. No match. No deref gymnastics.

It actually skips the clone

The key win isn’t just ergonomics — it’s performance. When the refcount is 1, no clone happens; the T is moved out of the allocation directly.

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

let solo = Arc::new(vec![1, 2, 3, 4, 5]);
let v: Vec<i32> = Arc::unwrap_or_clone(solo); // no allocation, just a move
assert_eq!(v, [1, 2, 3, 4, 5]);

let shared = Arc::new(vec![1, 2, 3]);
let _other = Arc::clone(&shared);
let v2: Vec<i32> = Arc::unwrap_or_clone(shared); // clones, because _other still holds a ref
assert_eq!(v2, [1, 2, 3]);

Also on Rc

The same method exists on Rc for single-threaded code — identical semantics, identical ergonomics:

1
2
3
4
5
use std::rc::Rc;

let rc = Rc::new(42);
let n: i32 = Rc::unwrap_or_clone(rc);
assert_eq!(n, 42);

Anywhere you were reaching for try_unwrap().unwrap_or_else(|a| (*a).clone()), reach for unwrap_or_clone instead. Shorter, clearer, and it avoids the clone when it can.

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