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
Cowis alreadyOwned, you get a direct&mutto the innerString. - If it’s
Borrowed,to_mutclones the slice into a freshString, swaps theCowover toOwned, 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:
| |
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:
| |
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:
| |
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.