#191 Jun 9, 2026

191. Accept &str, Not String — Take the Most General Borrow

A function that takes String forces every caller holding a &str to allocate just to call you. Take &str instead — and &[T] over &Vec<T> — and deref coercion lets everyone in for free.

The over-specific signature

This function only ever reads its argument, yet it demands an owned String:

1
2
3
fn greeting(name: String) -> String {
    format!("Hello, {name}!")
}

Now a caller with a string literal — the most common case — has to allocate a whole String just to satisfy the type:

1
let g = greeting("Ferris".to_string()); // pointless allocation

Worse, a caller who only has a borrow (say, a field of someone else’s struct) is stuck: they must .clone() or .to_owned() before they can call you, even though you never keep the value.

Take the borrow

If the body only reads, take &str. Deref coercion means &String and string literals both coerce to &str automatically:

1
2
3
4
5
6
7
fn greeting(name: &str) -> String {
    format!("Hello, {name}!")
}

let owned = String::from("Ferris");
assert_eq!(greeting("Ferris"), "Hello, Ferris!"); // literal, no alloc
assert_eq!(greeting(&owned), "Hello, Ferris!");    // &String coerces to &str

Zero allocations at the call site, and every kind of caller just works.

Same rule for slices

The exact parallel exists for vectors. Taking &Vec<T> locks callers into owning a Vec; taking &[T] accepts a Vec, an array, or any slice:

1
2
3
4
5
6
7
8
9
fn total(nums: &[i32]) -> i32 {
    nums.iter().sum()
}

let v = vec![1, 2, 3];
let a = [4, 5, 6];
assert_eq!(total(&v), 6);       // &Vec<i32> coerces to &[i32]
assert_eq!(total(&a), 15);      // array coerces too
assert_eq!(total(&v[1..]), 5);  // a sub-slice — impossible with &Vec

&Vec<T> couldn’t accept that array or that sub-slice at all. &[T] is strictly more flexible and costs nothing.

The general principle

Borrow the least specific type that still does the job: &str over &String, &[T] over &Vec<T>, &Path over &PathBuf. Owned types in arguments are for when the function actually needs to store the value. If it only reads, hand it the borrow — the caller keeps their allocation, and your function works with more types for free.

← Previous 190. Return Cow<str> — Allocate Only When You Actually Change Something Next → 192. impl Into<String> — Take Owned or Borrowed Without an Extra Allocation