Smart-Pointers

#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