Skip to content

Instantly share code, notes, and snippets.

@max-itzpapalotl
Last active January 16, 2024 22:33
Show Gist options
  • Save max-itzpapalotl/43a35ee9993fbd53bd660fa8f4f86996 to your computer and use it in GitHub Desktop.
Save max-itzpapalotl/43a35ee9993fbd53bd660fa8f4f86996 to your computer and use it in GitHub Desktop.
16. Traits

16. Traits

In this episode we present traits. They are used to define interfaces, which is useful for type-checked template code and for dynamic dispatch. In this way, traits are similar to inheritance in C++ as well as to concepts.

First example: Type-safe template code

trait Animal {
    fn make_noise(&mut self);
    fn eat(&mut self, how_much: i64);
    fn is_hungry(&self) -> bool;
}

#[derive(Debug)]
struct Dog {
    need_food: i64,
}

impl Animal for Dog {
    fn make_noise(&mut self) {
        println!("Bark!");
        self.need_food += 2;
    }
    fn is_hungry(&self) -> bool {
        self.need_food > 0
    }
    fn eat(&mut self, how_much: i64) {
        println!("Dog eating {}", how_much);
        self.need_food -= how_much;
    }
}

impl Dog {
    fn new() -> Dog {
        Dog { need_food: 0 }
    }
}

#[derive(Debug)]
struct Cat {
    need_food: i64,
}

impl Animal for Cat {
    fn make_noise(&mut self) {
        println!("Meow!");
        self.need_food += 1;
    }
    fn is_hungry(&self) -> bool {
        self.need_food > 0
    }
    fn eat(&mut self, how_much: i64) {
        println!("Cat eating {}", how_much);
        self.need_food -= how_much;
    }
}

impl Cat {
    fn new() -> Cat {
        Cat { need_food: 0 }
    }
}

fn live(mut a: impl Animal) {
    //fn live<T:Animal>(mut a : T) {
    a.make_noise();
    while a.is_hungry() {
        a.eat(1);
    }
    a.make_noise();
}

fn main() {
    let d = Dog::new();
    live(d);
    let c = Cat::new();
    live(c);
}

Second example: Type bounds

trait Animal {
    fn make_noise(&mut self);
    fn eat(&mut self, how_much: i64);
    fn is_hungry(&self) -> bool;
}

#[derive(Debug)]
struct Dog {
    need_food: i64,
}

impl Animal for Dog {
    fn make_noise(&mut self) {
        println!("Bark!");
        self.need_food += 2;
    }
    fn is_hungry(&self) -> bool {
        self.need_food > 0
    }
    fn eat(&mut self, how_much: i64) {
        println!("Dog eating {}", how_much);
        self.need_food -= how_much;
    }
}

impl Dog {
    fn new() -> Dog {
        Dog { need_food: 0 }
    }
}

#[derive(Debug)]
struct Cat {
    need_food: i64,
}

impl Animal for Cat {
    fn make_noise(&mut self) {
        println!("Meow!");
        self.need_food += 1;
    }
    fn is_hungry(&self) -> bool {
        self.need_food > 0
    }
    fn eat(&mut self, how_much: i64) {
        println!("Cat eating {}", how_much);
        self.need_food -= how_much;
    }
}

impl Cat {
    fn new() -> Cat {
        Cat { need_food: 0 }
    }
}

fn live(mut a: impl Animal) {
    //fn live<T:Animal>(mut a : T) {
    a.make_noise();
    while a.is_hungry() {
        a.eat(1);
    }
    a.make_noise();
}

//fn party(a : &mut impl Animal, b : &mut impl Animal) {
//fn party<T : Animal>(a : &mut T, b : &mut T) {    // wrong!
//fn party<T : Animal, U : Animal>(a : &mut T, b : &mut U) {
fn party<T, U>(a: &mut T, b: &mut U)
where
    T: Animal,
    U: Animal,
{
    a.make_noise();
    b.make_noise();
    while a.is_hungry() {
        a.eat(1);
    }
    while b.is_hungry() {
        b.eat(1);
    }
    b.make_noise();
    a.make_noise();
}

fn main() {
    {
        let d = Dog::new();
        live(d);
        let c = Cat::new();
        live(c);
    }
    {
        let mut d = Dog::new();
        let mut c = Cat::new();
        party(&mut d, &mut c);
    }
}

Third example: Dynamic dispatch (virtual functions)

trait Animal {
    fn make_noise(&mut self);
    fn eat(&mut self, how_much: i64);
    fn is_hungry(&self) -> bool;
}

#[derive(Debug)]
struct Dog {
    need_food: i64,
}

impl Animal for Dog {
    fn make_noise(&mut self) {
        println!("Bark!");
        self.need_food += 2;
    }
    fn is_hungry(&self) -> bool {
        self.need_food > 0
    }
    fn eat(&mut self, how_much: i64) {
        println!("Dog eating {}", how_much);
        self.need_food -= how_much;
    }
}

impl Dog {
    fn new() -> Dog {
        Dog { need_food: 0 }
    }
}

#[derive(Debug)]
struct Cat {
    need_food: i64,
}

impl Animal for Cat {
    fn make_noise(&mut self) {
        println!("Meow!");
        self.need_food += 1;
    }
    fn is_hungry(&self) -> bool {
        self.need_food > 0
    }
    fn eat(&mut self, how_much: i64) {
        println!("Cat eating {}", how_much);
        self.need_food -= how_much;
    }
}

impl Cat {
    fn new() -> Cat {
        Cat { need_food: 0 }
    }
}

fn live(mut a: Box<dyn Animal>) {
    a.make_noise();
    while a.is_hungry() {
        a.eat(1);
    }
    a.make_noise();
}

fn main() {
    let args : Vec<String> = std::env::args().collect();
    let a : Box<dyn Animal>;
    if args.len() < 2 {
        eprintln!("Usage: DOG or CAT as argument");
        return;
    }
    if args[1] == "CAT" {
        a = Box::new(Cat::new());
    } else if args[1] == "DOG" {
        a = Box::new(Dog::new());
    } else {
        eprintln!("Need DOG or CAT argument!");
        return;
    }
    live(a);
}

Fourth example: Dynamic return types

trait Animal {
    fn make_noise(&mut self);
    fn eat(&mut self, how_much: i64);
    fn is_hungry(&self) -> bool;
}

#[derive(Debug)]
struct Dog {
    need_food: i64,
}

impl Animal for Dog {
    fn make_noise(&mut self) {
        println!("Bark!");
        self.need_food += 2;
    }
    fn is_hungry(&self) -> bool {
        self.need_food > 0
    }
    fn eat(&mut self, how_much: i64) {
        println!("Dog eating {}", how_much);
        self.need_food -= how_much;
    }
}

impl Dog {
    fn new() -> Dog {
        Dog { need_food: 0 }
    }
}

#[derive(Debug)]
struct Cat {
    need_food: i64,
}

impl Animal for Cat {
    fn make_noise(&mut self) {
        println!("Meow!");
        self.need_food += 1;
    }
    fn is_hungry(&self) -> bool {
        self.need_food > 0
    }
    fn eat(&mut self, how_much: i64) {
        println!("Cat eating {}", how_much);
        self.need_food -= how_much;
    }
}

impl Cat {
    fn new() -> Cat {
        Cat { need_food: 0 }
    }
}

fn live(mut a: Box<dyn Animal>) -> Box<dyn Animal> {
    a.make_noise();
    while a.is_hungry() {
        a.eat(1);
    }
    a.make_noise();
    a
}

fn main() {
    let args : Vec<String> = std::env::args().collect();
    let a : Box<dyn Animal>;
    if args.len() < 2 {
        eprintln!("Usage: DOG or CAT as argument");
        return;
    }
    if args[1] == "CAT" {
        a = Box::new(Cat::new());
    } else if args[1] == "DOG" {
        a = Box::new(Dog::new());
    } else {
        eprintln!("Need DOG or CAT argument!");
        return;
    }
    let mut b = live(a);
    b.make_noise();
}

References:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment