mirror of
https://github.com/leonardomso/rust-skills.git
synced 2026-09-14 19:33:21 +08:00
0016d5cfb2
Includes rules for: - Ownership and borrowing patterns - Error handling with thiserror/anyhow - Memory management and allocation - API design following Rust guidelines - Async/Tokio patterns - Performance optimization - Naming conventions - Type safety - Testing strategies - Documentation standards - Project structure - Linting configuration - Common anti-patterns to avoid
3.5 KiB
3.5 KiB
own-cow-conditional
Use
Cow<'a, T>for conditional ownership
Why It Matters
Cow (Clone-on-Write) lets you avoid allocations when you might need to own data but usually don't. It holds either a borrowed reference or an owned value, cloning only when mutation is needed.
Bad
// Always allocates, even when input doesn't need modification
fn normalize_path(path: &str) -> String {
if path.contains("//") {
path.replace("//", "/") // Allocation needed
} else {
path.to_string() // Unnecessary allocation!
}
}
// Always clones the error message
fn format_error(code: u32) -> String {
match code {
404 => "Not Found".to_string(), // Unnecessary!
500 => "Internal Error".to_string(), // Unnecessary!
_ => format!("Error {}", code), // This one needs allocation
}
}
Good
use std::borrow::Cow;
// Only allocates when needed
fn normalize_path(path: &str) -> Cow<'_, str> {
if path.contains("//") {
Cow::Owned(path.replace("//", "/")) // Allocate
} else {
Cow::Borrowed(path) // Zero-cost borrow
}
}
// Static strings stay borrowed
fn format_error(code: u32) -> Cow<'static, str> {
match code {
404 => Cow::Borrowed("Not Found"), // No allocation
500 => Cow::Borrowed("Internal Error"), // No allocation
_ => Cow::Owned(format!("Error {}", code)), // Allocate only for unknown
}
}
Real-World Example from ripgrep
// https://github.com/BurntSushi/ripgrep/blob/master/crates/globset/src/pathutil.rs
pub(crate) fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
let last_slash = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0);
match *path {
Cow::Borrowed(path) => Some(Cow::Borrowed(&path[last_slash..])),
Cow::Owned(ref path) => {
let mut path = path.clone();
path.drain_bytes(..last_slash);
Some(Cow::Owned(path))
}
}
}
Clone-on-Write Pattern
use std::borrow::Cow;
fn process_text(text: Cow<'_, str>) -> Cow<'_, str> {
if text.contains("bad_word") {
// to_mut() clones if borrowed, returns &mut if owned
let mut owned = text.into_owned();
owned = owned.replace("bad_word", "***");
Cow::Owned(owned)
} else {
text // Pass through unchanged
}
}
// Usage
let borrowed: Cow<str> = Cow::Borrowed("hello world");
let result = process_text(borrowed); // No allocation!
let with_bad: Cow<str> = Cow::Borrowed("hello bad_word");
let result = process_text(with_bad); // Allocates only here
Cow with Collections
use std::borrow::Cow;
// Mixed borrowed/owned in a collection
fn collect_errors<'a>(
static_errors: &[&'static str],
dynamic_errors: Vec<String>,
) -> Vec<Cow<'a, str>> {
let mut errors: Vec<Cow<str>> = Vec::new();
// Static strings - no allocation
for &e in static_errors {
errors.push(Cow::Borrowed(e));
}
// Dynamic strings - take ownership
for e in dynamic_errors {
errors.push(Cow::Owned(e));
}
errors
}
When to Use Cow
| Situation | Use Cow? |
|---|---|
| Usually borrow, sometimes own | Yes |
| Always need owned data | No, just use owned type |
| Always borrow | No, just use reference |
| Hot path, avoiding all allocations | Yes |
| Returning static strings or formatted | Yes |
See Also
- own-borrow-over-clone - Prefer borrowing over cloning
- mem-avoid-format - Avoid format! when possible