Skip to content

← Generics and Traits step 11 of 24

Medium Primitives

Accept string-like values with AsRef

Update total_len so it accepts a slice of any element type implementing AsRef<str>:

pub fn total_len<S: AsRef<str>>(items: &[S]) -> usize
pub fn run(words: Vec<String>) -> Vec<usize>

Sum each item's UTF-8 byte length. Keep run unchanged: it calls total_len once with owned strings and once with borrowed string slices, returning [owned_total, borrowed_total]. Both totals must match. Count duplicates each time they occur; empty input returns [0, 0]. Test inputs have totals that fit in usize.

For ["héllo", "wörld"], return [12, 12]. Each word has five Unicode scalar values but six UTF-8 bytes. str::len() counts bytes; chars().count() would answer a different question.

Borrow the sequence as a slice

The starter takes &Vec<String>. That exposes a particular container even though the function only reads its elements. A slice parameter, &[S], can borrow elements from a vector, an array, or a subrange without creating a new collection.

A slice does not expose every vector operation, such as inspecting spare capacity. That is useful here: total_len does not need those capabilities. Prefer the borrowed view that supports the function's actual work, rather than treating &Vec or &String as always invalid.

Clippy's default ptr_arg lint identifies many such unnecessarily specific parameters. In this starter, however, the second call first fails type checking: &Vec<&str> does not match &Vec<String>. Fix the signature and body, then check the lints.

Borrow each element as str

The generic parameter S is the element type. Calling as_ref() on an item gives the &str view needed to read its length. String, &str, and Box<str> all support this bound.

The elements of one slice still have one type. Generics allow separate calls with different element types; they do not automatically create a mixed-type collection.

Here is a complete example of a wrapper exposing its stored text:

struct Label(String);

impl AsRef<str> for Label {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

fn byte_len<S: AsRef<str>>(value: &S) -> usize {
    value.as_ref().len()
}

fn main() {
    let label = Label(String::from("héllo"));
    assert_eq!(byte_len(&label), 6);
    assert_eq!(byte_len(&"abc"), 3);
    assert_eq!(byte_len(&Box::<str>::from("é")), 2);
}

AsRef is intended for cheap, infallible borrowing. The compiler checks the types and lifetimes, not a cost bound on the implementation. Use a separate fallible or allocating conversion API when that is what the operation needs.

Implementations do not automatically compose: A: AsRef<B> and B: AsRef<C> do not imply A: AsRef<C>. For example, Box<String> supports AsRef<String>, but that alone does not satisfy this function's AsRef<str> bound.

Borrowing does not mean no allocations anywhere

The calls to total_len borrow the sequences and their string contents. run still collects a Vec<&str> to demonstrate the second element type. That vector needs storage for its references; it does not copy the words' text.

Likewise, do not remove every & from generic calls merely because a lint mentions needless borrowing. Passing an owned value by value can move it. Follow the function's signature and whether the caller needs the value afterward.

Borrow has an additional contract

Borrow<T> also returns a reference, but requires matching equality, ordering, and hashing behavior between the owned and borrowed forms where those operations apply. This supports lookups such as:

use std::collections::HashMap;

fn main() {
    let mut counts: HashMap<String, usize> = HashMap::new();
    counts.insert(String::from("apple"), 3);
    assert_eq!(counts.get("apple"), Some(&3));
}

The map owns String keys, while the query borrows a str. String and str provide compatible equality and hashing, so the query can find the stored key without allocating a new string.

A wrapper with case-insensitive equality should not claim Borrow<str> while exposing ordinary case-sensitive text as its borrowed identity. It may still offer that text through AsRef<str>. The compiler does not prove the semantic agreement required by Borrow. An incorrect implementation can make lookups fail even though the code compiles.

Use AsRef here because the function needs a text view, not key identity. Submissions must compile, pass the output cases, and pass the configured Clippy checks.

Further reading: AsRef conversions and Borrow's equality and hashing contract.