RustArena

Step 4 of 6

Methods on generic types: impl<T>

Methods on a generic type go in an impl block that declares the type parameter too. Here's a stack, the data structure behind every "undo" button:

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

impl<T> Stack<T> {
    fn new() -> Self {
        Stack { items: Vec::new() }
    }

    fn push(&mut self, item: T) {
        self.items.push(item);
    }

    fn pop(&mut self) -> Option<T> {
        self.items.pop()
    }

    fn peek(&self) -> Option<&T> {
        self.items.last()
    }

    fn len(&self) -> usize {
        self.items.len()
    }
}

fn main() {
    let mut undo: Stack<String> = Stack::new();
    undo.push(String::from("type 'hello'"));
    undo.push(String::from("delete word"));
    println!("{} actions, last: {:?}", undo.len(), undo.peek());
    println!("undo {:?}", undo.pop());
    println!("undo {:?}", undo.pop());
    println!("undo {:?}", undo.pop());

    let mut moves = Stack::new();
    moves.push((3, 4));
    println!("{:?}", moves.peek());
}
2 actions, last: Some("delete word")
undo Some("delete word")
undo Some("type 'hello'")
undo None
Some((3, 4))

Methods for some types only

An impl block can add bounds, or name one concrete type. The methods inside then exist only for those types:

struct Pair<T> {
    a: T,
    b: T,
}

impl<T> Pair<T> {
    fn new(a: T, b: T) -> Self {
        Pair { a, b }
    }
}

impl<T: PartialOrd + Copy> Pair<T> {
    fn larger(&self) -> T {
        if self.a >= self.b { self.a } else { self.b }
    }
}

impl Pair<f64> {
    fn average(&self) -> f64 {
        (self.a + self.b) / 2.0
    }
}

fn main() {
    let temps = Pair::new(18.5, 23.0);
    println!("warmest {}, average {}", temps.larger(), temps.average());
    let letters = Pair::new('q', 'b');
    println!("later letter {}", letters.larger());
    let names = Pair::new(String::from("ana"), String::from("bo"));
    println!("{} and {}", names.a, names.b);
}
warmest 23, average 20.75
later letter q
ana and bo

Common mistakes

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

impl Stack<T> {
    fn len(&self) -> usize {
        self.items.len()
    }
}

fn main() {
    let s = Stack { items: vec![1] };
    println!("{}", s.len());
}
error[E0425]: cannot find type `T` in this scope
  help: you might be missing a type parameter
  | impl<T> Stack<T> {
  |     +++
struct Point<T> {
    x: T,
    y: T,
}

impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }
}

fn main() {
    let p = Point { x: 3, y: 4 };
    println!("{}", p.distance_from_origin());
}
error[E0599]: no method named `distance_from_origin` found for struct `Point<{integer}>` in the current scope
  = note: the method was found for `Point<f64>`

{integer} means "some integer type, not decided yet". Write Point { x: 3.0, y: 4.0 } to get a Point<f64>.

Your task

A chat app keeps the last few messages in a History<T> with a size limit. Write the methods in the starter:

Challenge 2

Add methods to Pair<T> in two impl blocks:

Challenge 3

Write a generic Shelf<T> from scratch: a container with a fixed capacity.

Previous: Generic structs and enums Next: Monomorphization: generics cost nothing at run time