Skip to content

← Generics and Traits step 5 of 24

Medium Primitives

A generic Stack, and conditional impl blocks

Types can be generic too, not just functions:

pub struct Stack<T> {
    items: Vec<T>,
}

Stack<i64> and Stack<String> are two entirely separate types, produced by the same monomorphisation machinery you met with generic functions. Neither can be assigned to the other.

The interesting part is the impl block. You will write two:

impl<T> Stack<T> { ... }              // available for every T
impl<T: Display> Stack<T> { ... }     // available only when T: Display

Read impl<T> Stack<T> carefully. The first <T> declares a type parameter for the block; the second is applying Stack to it. Two different jobs, same letter — this is the single most common early confusion. impl Stack<i64> (no declaration) is also legal and adds methods to that one concrete instantiation only.

The second block is a conditional API: render exists on Stack<i64> because i64: Display, and simply does not exist on a Stack<SomeOpaqueThing>. This is exactly how the standard library is built — Option<T>::unwrap_or_default only exists when T: Default, Vec<T>::dedup only when T: PartialEq. When you notice a std method “missing”, the usual reason is a bound you have not satisfied, not a gap in the library.

Your task

pub struct Stack<T>            // new, push, pop, len, is_empty
impl<T: Display> Stack<T>      // render
pub fn run(ops: Vec<(String, i64)>) -> Vec<String>

run drives a Stack<i64> through a list of (op, arg) pairs and returns one line per op:

op effect output
push push arg len=2
pop pop pop=7, pop=none
render [5,-3]
empty empty=true
anything else ?

render joins the elements bottom-to-top with , and run wraps that in square brackets. An empty stack renders as [].

Two clippy gates, and why they are the lesson

The starter compiles. It still fails, twice:

error: you should consider adding a `Default` implementation for `Stack<T>`
error: struct `Stack` has a public `len` method, but no `is_empty` method

Both are default-on (new_without_default, len_without_is_empty) and both fire only because the type is pub. On a private helper struct clippy stays quiet. That asymmetry is deliberate: these are API-design rules, and a type nobody else can name has no API to design. Since you are shipping Stack as public, you owe callers both fixes.

Write Default by hand. #[derive(Default)] on Stack<T> looks equivalent and is not: derive adds a T: Default bound, so Stack::<NotDefault>::default() would stop compiling even though an empty stack needs no T value at all. A hand-written impl<T> Default for Stack<T> { fn default() -> Self { Self::new() } } has no such bound. This is your first sighting of a trap that gets a whole article later in the track: #[derive] lies about its bounds.

One more error worth causing

Delete the items field and leave pub struct Stack<T>;. You get:

error[E0392]: type parameter `T` is never used

Rust insists every parameter be used, because an unused one would leave variance and drop behaviour undefined. The escape hatch when you genuinely want a phantom parameter is std::marker::PhantomData<T>, a zero-sized field that “uses” T without storing one.

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