Traits and Generics I

Overview

These notes provide an overview of the code examples we went over in lecture.

Here are the key takeaways:

LinkedList Example: Display and Drop

impl fmt::Display for LinkedList {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut current: &Option<Box<Node>> = &self.head;
        let mut result = String::new();
        loop {
            match current {
                Some(node) => {
                    result = format!("{} {}", result, node.value);
                    current = &node.next;
                },
                None => break,
            }
        }
        write!(f, "{}", result)
    }
}

impl Drop for LinkedList {
    fn drop(&mut self) {
        let mut current = self.head.take();
        while let Some(mut node) = current {
            current = node.next.take();
        }
    }
}

Here is the playground link

Deriving Traits with Point

#[derive(Debug, PartialEq, Clone, Copy)]
struct Point {
    x: f64,
    y: f64
}

impl Point {
    pub fn new(x: f64, y: f64) -> Self {
        Point {x: x, y: y}
    }
}

fn main() {
    let the_origin = Point::new(0.0, 0.0);
    let mut p = the_origin; // copy semantics!
    println!("p: {:?}, the_origin: {:?}", p, the_origin);
    println!("are they equal? => {}", p == the_origin);
    p.x += 10.0;
    println!("p: {:?}, the_origin: {:?}", p, the_origin);
    println!("are they equal? => {}", p == the_origin);
}

Here is the playground link

Defining Our Own ComputeNorm Trait and Add

pub trait ComputeNorm {
    fn compute_norm(&self) -> f64 {
        0.0
    }
}

// The following doesn't really make sense, it's just meant to illustrate the
// idea that the functions in traits can have default implementations!
impl ComputeNorm for Option<u32> {}

impl ComputeNorm for Point {
    fn compute_norm(&self) -> f64 {
        (self.x * self.x + self.y * self.y).sqrt()
    }
}

impl ComputeNorm for Vec<f64> {
    fn compute_norm(&self) -> f64 {
        // an example of functional rust syntax
        self.iter().map(|x| {x * x}).sum::<f64>().sqrt()
    }
}

impl Add for Point {
    type Output = Self; // an "associated type"
    fn add(self, other: Self) -> Self {
        Point::new(self.x + other.x, self.y + other.y)
    }
}

Here is the playground link

Generics with MatchingPair<T> and MyOption<T>

use std::fmt;
pub struct MatchingPair<T> {
    first: T,
    second: T
}

pub enum MyOption<T> {
    Sumthin(T), Nuthin
}

impl fmt::Display for MyOption<u32> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MyOption::Sumthin(num) => write!(f, "Sumthin({})", num),
            MyOption::Nuthin => write!(f, "Nuthin :("),
        }
        
    }
}


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

// an example of "where" syntax
impl fmt::Display for MatchingPair<char> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.first, self.second)
    }
}

fn main() {
    let ps_in_a_pod: MatchingPair<char> = MatchingPair::new('p', 'P');
    println!("two ps in a pod: {}", ps_in_a_pod);
    let my_some_five: MyOption<u32> = MyOption::Sumthin(5);
    let my_nuthin: MyOption<u32> = MyOption::Nuthin;
    println!("my_some_five: {}", my_some_five);
    println!("my_nuthin: {}", my_nuthin);
}

Here is the playground link

Trait Bounds

impl<T: fmt::Display> fmt::Display for MyOption<T> { // more general!
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MyOption::Sumthin(num) => write!(f, "Sumthin({})", num),
            MyOption::Nuthin => write!(f, "Nuthin :("),
        }
        
    }
}

impl<T> fmt::Display for MatchingPair<T> where T: fmt::Display {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {})", self.first, self.second)
    }
}

Here is the playground link