Skip to content

← Generics and Traits step 13 of 24

Medium Primitives

Choose the intended method through a wrapper

A wrapper and its dereference target can offer methods with the same name. Two traits can also define the same method. Practice selecting the intended implementation by completing counts in the supplied starter.

pub fn counts(slots: Vec<String>) -> Vec<usize>

Return four numbers in this order:

Position Meaning Supplied implementation
0 Number of non-empty strings Inventory::len
1 Total number of slots, including empty strings Vec::len
2 Sum of all string lengths in UTF-8 bytes Weight::total
3 Ten times the total slot count Bulk::total

Only edit the body of counts; keep the supplied types and implementations. Strings may contain arbitrary Unicode or control characters. Only "" is empty: spaces and newlines count as non-empty strings. Repeated strings each contribute to the totals. Inputs fit in memory, and both the byte sum and ten times the slot count fit in usize.

For ["axe", "", "rope"], return [2, 3, 7, 30]. For ["é", "", "ßß"], return [2, 3, 6, 30]: UTF-8 uses two bytes for each of these accented characters. No slots produce [0, 0, 0, 0].

Select the wrapper or the vector

Inventory implements Deref<Target = Vec<String>>, and it also defines its own len(&self). In this setup, inv.len() resolves to the wrapper's inherent method before lookup reaches the vector. That is the correct call for the first output, but the wrong one for the second.

To request the vector's length, use Vec::len(&inv) and let the reference coerce, or dereference once before lookup with (*inv).len().

This complete example demonstrates the same distinction with a small wrapper:

use std::ops::Deref;

struct Slots(Vec<String>);

impl Deref for Slots {
    type Target = Vec<String>;
    fn deref(&self) -> &Self::Target { &self.0 }
}

impl Slots {
    fn len(&self) -> usize {
        self.0.iter().filter(|s| !s.is_empty()).count()
    }
}

fn main() {
    let slots = Slots(vec![String::from("x"), String::new()]);
    assert_eq!(slots.len(), 1);
    assert_eq!(Vec::len(&slots), 2);
    assert_eq!((*slots).len(), 2);
}

These calls borrow the vector to read its length; the explicit * does not move its contents out. Method lookup depends on receiver types and borrowing, so avoid turning this example into a universal rule that any inherent method always wins. The method-call reference specifies the candidate order.

Select a trait explicitly

Both Weight and Bulk implement total(&self) for Inventory. With both traits in scope, inv.total() is ambiguous and produces E0034. Rust cannot infer which total you want from its return type; both return usize.

Use Weight::total(&inv) for the byte count and Bulk::total(&inv) for the bulk count. You can spell the first as <Inventory as Weight>::total(&inv) to identify both the implementing type and the trait. If the trait name itself is ambiguous, qualify its module path too.

The starter therefore has two issues: its second len() call chooses the wrong count, and its two total() calls do not compile. Resolve both while preserving the required output order.

The API design tradeoff

This wrapper deliberately gives len a different meaning from its target. A clearer domain API might use occupied_count() and slot_count() without dereferencing to a vector. That makes the choice visible in the method names.

Deref is useful for types that should transparently expose a borrowed target, including smart pointers and collections such as String and Vec. It is not class inheritance, and it does not transfer every trait implementation from the target to the wrapper. Implicit method access is a public API choice: new methods can collide, and removing the dereference implementation can break callers. Being a newtype alone does not settle those design questions.

Deref alone supplies shared access. This starter has no DerefMut, so it does not let you call the vector's clear() or push() through the wrapper. Adding DerefMut would permit mutable access too, potentially bypassing any update rules that the wrapper was meant to enforce.

An explicit accessor or AsRef can expose a selected borrowed view. Borrow additionally requires the owned and borrowed forms to agree on equality, ordering and hashing where applicable. Neither trait enables the target's methods through automatic receiver dereferencing as Deref does.