Files
Leonardo Maldonado 0016d5cfb2 Add comprehensive Rust best practices skill with 179 rules
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
2026-01-19 10:23:19 -03:00

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