DEV Community

loading...

Day31:Generic: trait - 100DayOfRust

Bo
Coder, Learner
・2 min read

Trait is like virtual-class in C++, or interface in lots of other languages.

trait Animal {
    fn sound(&self) -> String;
}

struct Cat {}

impl Animal for Cat {
    fn sound(&self) -> String {
        "Meow Meow".to_owned()
    }
}

fn main() {
    let cat = Cat {};
    println!("Cat {}", cat.sound());
}
Enter fullscreen mode Exit fullscreen mode

Run it:

Cat Meow Meow
Enter fullscreen mode Exit fullscreen mode

We can use impl Trait as the return type:

...

fn adopt_cat() -> impl Animal {
    Cat {}
}

fn main() {
    let cat = adopt_cat();
    println!("Cat {}", cat.sound());
}
Enter fullscreen mode Exit fullscreen mode

If we have 2 types of animal here, can we return different types with impl Animal? let's try it:

trait Animal {
    fn sound(&self) -> String;
}

struct Cat {}

impl Animal for Cat {
    fn sound(&self) -> String {
        "Meow Meow".to_owned()
    }
}

struct Dog {}

impl Animal for Dog {
    fn sound(&self) -> String {
        "Bark Bark".to_owned()
    }
}

fn adopt_animal(animal: &str) -> impl Animal {
    if animal == "cat" {
        Cat {}
    } else {
        Dog {}
    }
}

fn main() {
    let animal = adopt_animal("cat");
    println!("Animal {}", animal.sound());
}
Enter fullscreen mode Exit fullscreen mode

Here we defined Cat and Dog structs, and implemented the "Animal" trait for them, and in the adopt_animal function, our return type is impl Animal, because we know both Cat and Dog implemented the Animal trait.

Let's run it:

   |
22 | /     if animal == "cat" {
23 | |         Cat {}
   | |         ------ expected because of this
24 | |     } else {
25 | |         Dog {}
   | |         ^^^^^^ expected struct `Cat`, found struct `Dog`
26 | |     }
   | |_____- if and else have incompatible types
   |
Enter fullscreen mode Exit fullscreen mode

Ah, we got an error here. Turns out although both Cat and Dog implemented the Animal trait, it will find the first return type and treat that as the return type. In our case, Rust treats Cat object as the returned type, so later it finds we might also return Dog, it reports an error.

This is not really utilizing generics. If we want to return either type, we need to change the return type to Box<dyn Animal>:

...

fn adopt_animal(animal: &str) -> Box<dyn Animal> {
    if animal == "cat" {
        Box::new(Cat {})
    } else {
        Box::new(Dog {})
    }
}

fn main() {
    let animal = adopt_animal("cat");
    println!("Animal {}", animal.sound());
    let animal = adopt_animal("dog");
    println!("Animal {}", animal.sound());
}
Enter fullscreen mode Exit fullscreen mode

Now we are able to run it:

Animal Meow Meow
Animal Bark Bark
Enter fullscreen mode Exit fullscreen mode

P.S. We can also use trait to implement the "Template Method" design pattern, I wrote an article here before.

Discussion (0)

Forem Open with the Forem app