po4yka/rust-skills/rust-copy-on-write
Claude Code and agent skill by po4yka/rust-skills
- community
Checking that the SKILL.md file is still in the repository.
Use when choosing between borrowed and owned data at an API boundary (Cow<str>, Cow<[T]>, to_mut, into_owned), including a function that a profile shows allocating a new String or Vec on every call although most inputs come back unchanged, or when clone cost drives a data-structure choice (Arc<str>, the persistent collection crates im, imbl, rpds). Not for other allocation cuts at a named hot spot (use rust-hot-path). Triggers on "copy-on-write", "borrowed or owned", "clone cost", "structural sharing".
Install
npx vskill@latest install po4yka/rust-skills/rust-copy-on-writeSKILL.md
Rust copy-on-write
Every number below was measured on rustc 1.98.1, edition 2024, aarch64-apple-darwin, release profile, with the counting allocator in Measure before you decide. Allocation counts repeat exactly across runs. Times do not; a range is the observed range. Re-measure on your target.
Route the decision to a section
| You are about to | Go to | |
|---|---|---|
Return String where most calls change nothing | `Cow` in return position | |
| Chain two or more string or slice transforms | `Cow` in argument position | |
Write cow.to_mut() in front of a method | `to_mut()` | |
Put a Cow field in a struct, or share a value a cache or a task holds | A `Cow` field infects the struct | |
Write Cow<'_, String> or Cow<'_, Vec<T>> | The `Cow` parameter is the borrowed half | |
Write impl Borrow<..>, or debug a HashMap::get that misses a present key | references/borrow-and-toowned.md | |
Write fn with(&self) -> Self for a version history | `Cow` is not structural sharing | |
Choose between Vec and a persistent collection | Persistent collections | |
Read a profile that already names __rust_alloc | The rust-hot-path skill, when it is installed: capacity, buffer reuse, SmallVec |
Verify the decision
| Claim | Check | A pass does not prove | |
|---|---|---|---|
The Cow shape saves allocations | The counting allocator below, on real input, with the Borrowed path in the run | That callers keep the borrow. Read every call site for .into_owned() and .to_string() | |
No Cow names an owned type | cargo clippy (clippy::owned_cow) plus the grep in that section | Anything about custom ToOwned types. Anything about public items, unless clippy.toml sets avoid-breaking-exported-api = false; the grep covers them | |
No to_mut() runs before a &self method | The grep in that section, then a read of each hit | That each to_mut() sits inside the branch that writes | |
A Borrow<X> key finds its entries | A test that compares hash_one of the key with hash_one of its borrowed form on one RandomState, for a key in each letter case that eq folds. Then a get through the borrowed form | A correct contract, from a get test alone: it passes about 1 run in 150 with a broken contract. A lookup through the owned key proves nothing | |
| The collection crate's resolved tree is clean | cargo deny --config deny.toml --locked check advisories with unsound = "all" in deny.toml, or cargo audit --deny warnings, on the lockfile | A clean tree, if you drop the setting: default cargo-deny misses the transitive sized-chunks unsound advisory in an im tree, and default cargo audit exits 0 on every informational advisory. A failure does not prove that your code reaches the flagged API |
Measure before you decide
Every rule here is a hit-rate rule. A hit rate is a property of your workload, not of the type. Put this allocator in a bench binary, run your real input through both shapes, and compare the counts.
Run it in release. dhat gives the same counts with less code; the rust-performance skill covers it, when it is installed.
Cow in return position
Cow<'_, str> pays only on the branch that changes the data. Measured over 1000 path strings where 5% need a rewrite, generated by (0..1000).map(|i| if i % 20 == 0 { format!("path\\{i}") } else { format!("path/{i}") }). The lengths sum to 7890 bytes:
| Return shape | Allocations | Bytes | |
|---|---|---|---|
String | 1000 | 7890 | |
Cow<'_, str> | 50 | 394 | |
Cow<'_, str>, and the caller writes .into_owned() | 1000 | 7890 |
The third row is the rule. Cow in return position wins only when the caller can hold the borrow. The moment a call site writes .into_owned() or .to_string(), the count returns to the String figure, and the Cow is a branch plus a lifetime parameter for nothing.
into_owned() costs 0 allocations on a Cow::Owned and 1 on a Cow::Borrowed. Call it once at the boundary where the source buffer dies, not inside the loop that produced the Cow.
Two checks before you convert a signature:
- Measure the branch split.
Cowallocates exactly (modification rate x N) times, and
String always N: 0 at 0%, 250 at 25%, 600 at 60%, 1000 at 100%. It saves nothing only at 100%. The 20x above comes from a 5% rate.
- Read the call sites. One
.into_owned()in the only caller cancels the whole change.
Cow is free in size. Measured on aarch64: &str 16 bytes, String 24, Cow<'_, str> 24, Cow<'_, [u8]> 24. Enum layout is an unspecified implementation detail, and Rust 1.97 changed the layout of some enums. If you depend on the size, assert it in a form that holds on every target: const _: () = assert!(size_of::<Cow<'_, str>>() == size_of::<String>());.
Cow in argument position
A single Cow return is a small win. Cow -> Cow through a chain is a large one, because each stage passes its input through untouched instead of re-allocating between stages. Measured over the same 1000 inputs with three chained stages. The String row runs the same three stages with fn(&str) -> String signatures:
| Chain | Allocations | Bytes | |
|---|---|---|---|
Cow<'a, str> -> Cow<'a, str>, 3 stages | 50 | 394 | |
String -> String, 3 stages | 3000 | 23670 |
Only to_slashes allocates on this input set. The other two stages pass their input through, so the chain costs what one stage costs. The String chain pays 7890 bytes per stage whether the stage changes anything or not.
Take Cow<'a, str> by value in a chain stage. A stage that takes &Cow<'a, str> must clone an Owned input only to pass it through, which is the cost the chain exists to avoid.
to_mut() is for &mut self methods only
to_mut() on a Cow::Borrowed clones the borrowed data into an owned value, then returns &mut. A &self method called on that &mut allocates a second time and drops the first.
This form allocates twice:
This form allocates once. Deref reaches str::replace with no to_mut() at all:
Use to_mut() only for a method that takes &mut self: push_str, Vec::push, sort. to_mut().push_str("!") on a borrowed 13-byte string costs 2 allocations and 39 bytes: to_mut() allocates 13, then push_str reallocates to 26. When you know the final length, String::with_capacity(s.len() + 1) followed by push_str and push costs 1 allocation and 14 bytes. Keep the call inside the branch that writes, not above it.
to_mut() on an already-Owned value is free, so a benchmark that starts from Cow::Owned shows no difference and hides both defects. Benchmark the Borrowed path.
Grep for the misuse. Every hit needs a check that the method takes &mut self:
A Cow field infects the struct with a lifetime
struct Header<'a> { name: Cow<'a, str> } is a borrowing struct. The lifetime spreads to every signature that mentions it, exactly like a &'a mut T field. The rust-discipline skill, when it is installed, covers lifetime infection in general. Without it, give a long-lived struct an owned field (String) or a generic parameter, so it carries no lifetime.
Two failures arrive together:
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/rust-copy-on-write/SKILL.md
Popularity
- Repository stars
- 2
- Repository forks
- 1
- 7-day trend score
- 5
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/po4yka/rust-skills/rust-copy-on-write)