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))
impl<T> Stack<T>reads "for any typeT, here are the methods ofStack<T>". The first<T>declares the parameter,Stack<T>uses it. It's the same idea asfn last<T>.- Inside,
Tis the element type:pushtakes aT,popgives one back,peeklends a&T. SelfisStack<T>, sonewdoesn't have to repeat the parameter.undogets its type from the annotationStack<String>.moveshas no annotation: the compiler seespush((3, 4))on the next line and infersStack<(i32, i32)>.
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
- Every
Pairhasnew. largerexists only whenTisPartialOrd + Copy.Pair<String>is a fine type, but it has nolargermethod.impl Pair<f64>has no<T>at all: it's for exactly one type, soaverageexists only onPair<f64>. Addinga + band dividing by2.0only makes sense for floats anyway.
Common mistakes
impl Stack<T>withoutimpl<T>. The compiler readsTas the name of a real type, which doesn't exist:
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> {
| +++
- Calling a method that belongs to another concrete type.
distance_from_origindefined inimpl Point<f64>doesn't exist on aPointof integers:
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:
new(limit): an empty historyrecord(item): adds the item; if there are now more thanlimititems, removes the oldest (index 0, withself.items.remove(0))latest(): the newest item,oldest(): the oldest one still kept (bothOption<&T>)len(): how many items are kept
Challenge 2
Add methods to Pair<T> in two impl blocks:
- in an
impl<T: PartialOrd + Copy> Pair<T>block:larger(&self) -> Tandsmaller(&self) -> T - in an
impl<T> Pair<T>block (no bounds, so it works forStrings too):swap(self) -> Pair<T>, which returns a pair withaandbexchanged
Challenge 3
Write a generic Shelf<T> from scratch: a container with a fixed capacity.
struct Shelf<T>withitems: Vec<T>andcapacity: usize- in
impl<T> Shelf<T>:new(capacity: usize) -> Self,len(&self) -> usize, andadd(&mut self, item: T) -> Result<(), T>, which stores the item, or, when the shelf is full, gives the item back inErrso it isn't lost - in
impl Shelf<f64>(a shelf of prices):total(&self) -> f64, the sum of all items
Previous: Generic structs and enums Next: Monomorphization: generics cost nothing at run time