Skip to content

← Generics and Traits step 12 of 24

Medium Primitives

Expose a string view with Deref

Implement Deref<Target = str> for the supplied Username wrapper. Keep stats and shout_len unchanged so the existing method calls and borrowed argument work through your implementation.

pub struct Username(pub String);
pub fn stats(names: Vec<String>) -> Vec<usize>

For each input name, stats returns the sum of:

  1. Its original UTF-8 byte length, from u.len().
  2. Its number of Unicode scalar values, from u.chars().count().
  3. Its uppercase UTF-8 byte length, from the supplied shout_len(&u).

Return one result per name in input order. Preserve duplicates. Empty input produces an empty vector, and an empty name produces zero. Names can contain arbitrary Unicode, whitespace and control characters; do not trim or normalize them. Input sizes and the sums fit in usize.

Bytes and scalar values are different counts

Name Original bytes Scalar values Uppercase bytes Result
ada 3 3 3 (ADA) 9
héllo 6 5 6 (HÉLLO) 17
ß 2 1 2 (SS) 5

chars() counts Unicode scalar values, not user-perceived characters. For example, an e followed by a combining acute accent has two scalar values. An emoji sequence may also contain several. String length counts bytes.

The helper uses Rust's str::to_uppercase(), which performs Unicode uppercase mapping without a locale setting. A mapping may expand one scalar value into several, and the byte length can change too. Use the helper rather than an ASCII-only conversion. Unicode tables can change with Rust versions; the examples here were checked with Rust 1.98.1.

Borrow the inner string

Deref has an associated Target and a method returning a reference to it. This complete example shows the same pattern with a separate wrapper:

use std::ops::Deref;

struct Text(String);

impl Deref for Text {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.0.as_str()
    }
}

fn byte_len(text: &str) -> usize {
    text.len()
}

fn main() {
    let text = Text(String::from("héllo"));
    assert_eq!(text.len(), 6);
    assert_eq!(text.chars().count(), 5);
    assert_eq!(byte_len(&text), 6);
}

Returning &self.0 also works: the expected &str lets Rust coerce the borrowed String to its string slice. Both forms borrow existing storage; neither clones the string. The reference cannot outlive the borrow of the wrapper.

Target = str is a requirement of this exercise, not a rule that every dereference target must be unsized. Choosing String would permit a further dereference to str, but would expose a different target API. The output tests alone do not distinguish those choices.

Two uses of Deref in the starter

The len() and chars() calls use method lookup: Rust can follow the wrapper's dereference target to find shared str methods. The shout_len call instead has an expected &str argument type, so &Username can undergo deref coercion there. Before your implementation exists, those methods are missing and the argument has the wrong type.

Keep shout_len(&u). Writing shout_len(&*u) also type-checks, but Clippy's explicit_auto_deref warning identifies the unnecessary explicit step in this call. That does not make explicit dereferencing wrong everywhere.

Generic trait bounds are a separate issue: a wrapper does not gain all of its target's trait implementations. A generic call that infers the wrapper type may therefore fail where a concrete &str parameter accepts it. Providing an explicit target type can sometimes supply the needed coercion context; there is no automatic search through dereference targets to satisfy an arbitrary trait bound.

Choose implicit access deliberately

A dereference implementation becomes part of the wrapper's public API. It should be cheap, unsurprising and free of unexpected failures, because callers may invoke it implicitly. These are design expectations, not compiler guarantees: custom implementations can be costly or panic.

String, Vec and smart pointers demonstrate useful dereference APIs. A domain wrapper may instead benefit from an explicit accessor, especially if target methods would conflict with its own meaning. In this exercise, add the requested trait implementation rather than new inherent len or chars methods. A new method could delegate faithfully or change the behavior, but neither would practice the missing dereference implementation.