Files
leonardomso__rust-skills/rules/anti-string-for-str.md
T
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

2.6 KiB

anti-string-for-str

Don't accept &String when &str works

Why It Matters

&String is strictly less flexible than &str. A &str can be created from String, &str, literals, and slices. A &String requires exactly a String. This forces callers to allocate when they might not need to.

Bad

// Forces callers to have a String
fn greet(name: &String) {
    println!("Hello, {}", name);
}

// Caller must allocate
greet(&"Alice".to_string());  // Unnecessary allocation
greet(&name);                 // Only works if name is String

// In struct
struct Config {
    name: String,
}

impl Config {
    fn set_name(&mut self, name: &String) {  // Too restrictive
        self.name = name.clone();
    }
}

Good

// Accept &str - works with String, &str, literals
fn greet(name: &str) {
    println!("Hello, {}", name);
}

// All these work
greet("Alice");           // String literal
greet(&name);             // &String coerces to &str
greet(name.as_str());     // Explicit &str

// In struct
impl Config {
    fn set_name(&mut self, name: &str) {
        self.name = name.to_string();
    }
    
    // Or accept owned String if caller usually has one
    fn set_name_owned(&mut self, name: String) {
        self.name = name;
    }
    
    // Or be generic
    fn set_name_into(&mut self, name: impl Into<String>) {
        self.name = name.into();
    }
}

Deref Coercion

String implements Deref<Target = str>, so &String automatically coerces to &str:

fn takes_str(s: &str) { }

let owned = String::from("hello");
takes_str(&owned);  // &String -> &str via Deref

When to Accept &String

Rarely. Maybe if you need String-specific methods:

fn needs_capacity(s: &String) -> usize {
    s.capacity()  // Only String has capacity()
}

But usually you'd take &str and let the caller manage the String.

Pattern: Flexible APIs

// Most flexible: accept anything that can become &str
fn process(input: impl AsRef<str>) {
    let s: &str = input.as_ref();
    // ...
}

process("literal");
process(String::from("owned"));
process(&some_string);

Similar Anti-patterns

Anti-pattern Better
&String &str
&Vec<T> &[T]
&Box<T> &T
&PathBuf &Path
&OsString &OsStr

Clippy Detection

[lints.clippy]
ptr_arg = "warn"  # Catches &String, &Vec, &PathBuf

See Also