Skip to content

← Ownership II: Borrowing and the Borrow Checker step 10 of 24

Easy Primitives

&str not &String, &[T] not &Vec<T> — deref coercion and ptr_arg

pub fn longest_common_prefix(words: &[String]) -> String

Return the longest prefix shared by every word, measured in characters (chars), not bytes. An empty list gives "".

[]                          -> ""
["flower","flow","flight"]  -> "fl"
["dog","cat"]               -> ""
["abc"]                     -> "abc"
["héllo","héro"]            -> "hé"

Character-wise matters: "héllo" and "héro" share the two characters h and é, and é is two bytes. A byte-wise comparison of two different accented letters could stop halfway through a multi-byte character, and slicing there panics. Comparing chars makes that impossible.

The whole function is already written for you and it passes every test. It still fails the grade.

The lint

error: writing `&String` instead of `&str` involves a new object where a slice will do
  --> src/lib.rs:1:23
   |
 1 | fn shared_prefix_len(a: &String, b: &String) -> usize {
   |                         ^^^^^^^
   = note: `-D clippy::ptr-arg` implied by `-D warnings`

clippy::ptr_arg is warn-by-default, which under -D warnings means your submission does not pass. It is the highest-frequency API-design lesson in Rust, and this problem exists so that the grader teaches it to you mechanically rather than a reviewer teaching it to you socially.

Why &String is a worse parameter type than &str

A String is three words: pointer, length, capacity — plus a heap allocation. A &String is a pointer to those three words. A &str is two words: pointer and length, pointing directly at the bytes.

&String ──▶ [ ptr | len | cap ] ──▶ "hello"
&str    ──────────────────────────▶ "hello"  (+ len)

So &String is a pointer chase you did not need. But indirection is the small problem. The big problem is that &String narrows who can call you:

fn takes_string(s: &String) { }
fn takes_str(s: &str) { }

takes_string("literal");        // no. a literal is a &'static str.
takes_str("literal");           // yes
takes_str(&some_string);        // yes
takes_str(&big[3..8]);          // yes — a sub-slice, no allocation

A caller holding a literal, a slice, or text borrowed from a buffer has to allocate a String just to call your &String function. You have made every caller pay for a constraint you did not need.

The same argument, identically, for &Vec<T> versus &[T], and for &PathBuf versus &Path. Same lint, same reasoning.

The magic that makes the fix free: deref coercion

Here is the part that makes this lesson land. Change the helper’s parameters from &String to &str and then look at the call site:

n = n.min(shared_prefix_len(first, w));   // `first` and `w` are &String

You do not have to touch it. Not one character.

That is deref coercion: when a function expects &U and you supply &T where T: Deref<Target = U>, the compiler silently inserts the dereference. String: Deref<Target = str>, so &String becomes &str at the boundary, free, at compile time. Likewise Vec<T>: Deref<Target = [T]>, so &Vec<i64> becomes &[i64], and &mut Vec<i64> becomes &mut [i64].

This is why the advice is unambiguous rather than a trade-off. Widening your parameter from &String to &str costs your existing callers nothing and gains you every caller who has a &str already. There is no other side to the argument.

It is also the answer to a mystery you have already met: why words.join(" ") works when join is defined on [T] and not on Vec<T>, and why some_string.trim() works when trim is defined on str. Method lookup walks the deref chain. It has been quietly doing this since your first day.

When &String is not wrong

Clippy is a linter, not a proof system, and it knows about the exception. If your function body genuinely uses String-only capability — capacity, reallocation, ownership transfer — then &String (or, more likely, &mut String or String) is the correct type and clippy will not fire. The lint is suppressed when the body needs the concrete type. It has historically had false positives around this (rust-clippy#8482 is the canonical thread), which is worth knowing so that you evaluate the suggestion rather than obeying it reflexively.

In this problem the helper only reads characters, so &str is plainly right and clippy is plainly correct.

About the outer signature

longest_common_prefix(words: &[String]) takes a slice of owned Strings — the collection is borrowed, the elements are owned by the caller’s vector. That is normal and correct. It is &Vec<String> that would have been flagged.

Remember the grade is compile + tests + clippy -D warnings.

Loading visualization…