Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust - Traits & Generics

Traits

A trait defines shared behaviour - similar to interfaces in other languages.

#![allow(unused)]
fn main() {
trait Summary {
    fn summarize(&self) -> String;

    // Default implementation (can be overridden)
    fn preview(&self) -> String {
        format!("{}...", &self.summarize()[..20])
    }
}

struct Article {
    title: String,
    content: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("{}: {}", self.title, self.content)
    }
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("@{}: {}", self.username, self.content)
    }
}
}

Traits as parameters

#![allow(unused)]
fn main() {
// impl Trait syntax (shorthand)
fn notify(item: &impl Summary) {
    println!("{}", item.summarize());
}

// Trait bound syntax (equivalent, more flexible)
fn notify<T: Summary>(item: &T) {
    println!("{}", item.summarize());
}

// Multiple bounds
fn notify(item: &(impl Summary + std::fmt::Display)) { }
fn notify<T: Summary + std::fmt::Display>(item: &T) { }

// where clause - cleaner with many bounds
fn notify<T, U>(t: &T, u: &U)
where
    T: Summary + Clone,
    U: std::fmt::Debug,
{ }
}

Returning traits

#![allow(unused)]
fn main() {
// Return something that implements Summary (concrete type hidden)
fn make_summary() -> impl Summary {
    Tweet {
        username: String::from("gbraad"),
        content: String::from("Hello!"),
    }
}
}

Common standard library traits

TraitPurpose
Display{} formatting
Debug{:?} formatting - #[derive(Debug)]
Clone.clone() - #[derive(Clone)]
Copyimplicit copy semantics
PartialEq / Eq== operator
PartialOrd / Ord<, > operators
Iterator.next(), enables for loops and adaptors
From / Intotype conversions
Default.default() zero value

Generics

#![allow(unused)]
fn main() {
// Generic function
fn largest<T: PartialOrd>(list: &[T]) -> &T {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

// Generic struct
struct Pair<T> {
    first: T,
    second: T,
}

impl<T> Pair<T> {
    fn new(first: T, second: T) -> Self {
        Pair { first, second }
    }
}

// Conditional implementation (only for T that implements Display + PartialOrd)
impl<T: std::fmt::Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.first >= self.second {
            println!("largest: {}", self.first);
        } else {
            println!("largest: {}", self.second);
        }
    }
}
}

Iterators

Any type implementing Iterator gets all adaptor methods for free:

#![allow(unused)]
fn main() {
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}
}
#![allow(unused)]
fn main() {
let v = vec![1, 2, 3, 4, 5];

// Adaptors (lazy - don't execute until consumed)
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
let evens: Vec<&i32>  = v.iter().filter(|x| *x % 2 == 0).collect();
let sum: i32           = v.iter().sum();
let total: i32         = v.iter().fold(0, |acc, x| acc + x);

// any / all
let has_even = v.iter().any(|x| x % 2 == 0);
let all_pos  = v.iter().all(|x| *x > 0);

// Chaining
let result: Vec<i32> = v.iter()
    .filter(|&&x| x > 2)
    .map(|&x| x * 10)
    .collect();
}

Closures

#![allow(unused)]
fn main() {
let add = |x, y| x + y;
let double = |x: i32| -> i32 { x * 2 };

// Closures capture their environment
let multiplier = 3;
let triple = |x| x * multiplier;   // captures multiplier by reference

// move closure - takes ownership of captured variables
let s = String::from("hello");
let greet = move || println!("{s}");
}

Closure trait bounds:

  • Fn - borrows immutably (can call multiple times)
  • FnMut - borrows mutably (can call multiple times)
  • FnOnce - takes ownership (can only call once)
#![allow(unused)]
fn main() {
fn apply<F: Fn(i32) -> i32>(f: F, value: i32) -> i32 {
    f(value)
}

let result = apply(|x| x * 2, 5);   // 10
}