2

first of all the obligatory "I am new to Rust": I am.

So I have the following problem:

I have two(or more) structs of data, that all implement some common behaviour in addition their own behaviour. I have a list of these structs (or rather: of the 'supertype'), I need to access some of their shared behaviour and some of their individual behaviour. My Question is: how do I do that in Rust.

To further illustrate my question I have come up with a code comparision between Kotlin and Rust. Kotlin works as I want it to, Rust does not (yet).

In Kotlin the code may look like this(using the plain, old inheritance abstraction):

interface Animal {
    fun eat()
    fun sleep()
}
class Cat(val name: String) : Animal {
    fun meow()              { println("meow") }

    override fun eat()      { println("cat $name is eating fish(or lasagne)") }
    override fun sleep()    { println("cat $name sleeps inside") }
}
class Lion(val tag_id: Int) : Animal {
    fun roar()              { println("roar") }

    override fun eat()      { println("lion(tag=${tag_id} is eating gazelle") }
    override fun sleep()    { println("lion(tag=${tag_id} sleeps outside") }
}

var animals: MutableList<Animal> = ArrayList()
fun main() {
    animals.add(Cat("Garfield"))
    animals.add(Lion(12))
    //later:
    for (animal in animals) {
        animal.sleep()
        if (animal is Lion)
            animal.roar()
    }
}

In Rust I came up with the following code (which does not allow an 'instance_of' type function):

trait Animal {
    fn eat(&self);
    fn sleep(&self);
}

struct Cat {
    name: String
}
impl Cat {
    fn meow(&self)      { println!("meow") }
}
impl Animal for Cat {
    fn eat(&self)       { println!("cat {} is eating fish(or lasagne)", self.name) }
    fn sleep(&self)     { println!("cat {} sleeps inside", self.name) }
}

struct Lion {
    tag_id: usize
}
impl Lion {
    fn roar(&self)      { println!("roar") }
}
impl Animal for Lion {
    fn eat(&self)       { println!("lion(tag={}) is eating fish(or lasagne)", self.tag_id) }
    fn sleep(&self)     { println!("lion(tag={}) sleeps inside", self.tag_id) }
}

fn main() {
    let animals:Vec<Box<dyn Animal>> = vec![
                Box::new(Cat {name: "Garfield".to_string()}),
                Box::new(Lion {tag_id: 12})
    ];
    //later:
    for animal in animals {
        animal.sleep()
        //HOW DO I ACCESS THE CONCRETE STRUCT HERE?
    }
}

Playground

I realize this may be a stupid question, or show how I am 'still trapped in the non-Rust thinking', but I am at kind of an Impass here and need just a little bit of assistence.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • This may help: https://stackoverflow.com/questions/52005382/what-is-a-function-for-structs-like-javas-instanceof – user May 28 '20 at 18:03

4 Answers4

1

Try doing it with composition instead

trait Animal {
    fn voicebox(&self) -> Voicebox;
}
enum Voicebox {
    CatVoicebox, LionVoicebox
}

impl Voicebox {
    fn make_sound(&self) {
        match *self {
            Voicebox::CatVoicebox => println!("meow"),
            Voicebox::LionVoicebox => println!("roar!")
        }
    }
}

impl Animal for Cat {
    fn voicebox(&self) -> Voicebox {
      Voicebox::CatVoicebox
    }
}

impl Animal for Lion {
    fn voicebox(&self) -> Voicebox {
        Voicebox::LionVoicebox
    }
}

fn main() {
    let animals:Vec<Box<dyn Animal>> = vec![Box::new(Cat {name: "Garfield".to_string()}), Box::new(Lion {tag_id: 12})];
    //later:
    for animal in animals {
        animal.sleep();
        match animal.voicebox() {
            vb@Voicebox::LionVoicebox => vb.make_sound(),
            _ => ()
        }
    }
}

Output:

cat Garfield sleeps inside
lion(tag=12) sleeps inside
roar!

Rust playground

user
  • 7,435
  • 3
  • 14
  • 44
  • I think I get what you are doing and I think it answers my question, but the code does not compile as is and the playground link is just mine again.. – Justin Bennett May 28 '20 at 18:48
  • @JustinBennett Oh sorry, I thought it saved automatically. I'll update the link – user May 28 '20 at 18:48
  • @JustinBennett I fixed the link now – user May 28 '20 at 18:53
  • thanks a lot - this is definitely a solution for a subset of my problem. Just what do I do if I the individual tasks of the 'subtypes' are disjunct (i.e. Cat has additionally the individual methods 'run', 'backflip' and 'jump', and Lion has 'swim', 'walk' and 'be_king')?? – Justin Bennett May 28 '20 at 18:59
  • 1
    @Justin If they're that different, then why are you stuffing them into the same vector? It's always possible to come up with scenarios where any given design falls short. Your task as a designer is to decide which properties are *relevant* to the problem you are *really* solving, and pick a design that has those. – trent May 29 '20 at 02:53
  • That is what I am trying to do. And since I was not able to using the design I am used to I am trying to discuss it here. Preferably in a manner that can help others in the future. – Justin Bennett May 29 '20 at 09:13
1

Type checked conditions can be implemented by providing a as_<type> function for every subtype:

trait Animal {
    fn eat(&self);
    fn sleep(&self);
    fn as_roaring(&self)->Option<&dyn Roaring>;
    fn as_meowing(&self)->Option<&dyn Meowing>;
}

The implementation of Lion would look like:

impl Animal for Lion {
    fn eat(&self)       { println!("lion(tag={}) is eating fish(or lasagne)", self.tag_id) }
    fn sleep(&self)     { println!("lion(tag={}) sleeps inside", self.tag_id) }
    fn as_roaring(&self)->Option<&dyn Roaring> {Some(self)}
    fn as_meowing(&self)->Option<&dyn Meowing> {None}
}

and of the loop:

    for animal in animals {
        animal.sleep();
        if let Some(roaring) = animal.as_roaring() {
          roaring.roar();
        }
    }

Playground.

CoronA
  • 7,717
  • 2
  • 26
  • 53
1

You can try something like this

use std::any::Any;

trait Animal {
    fn eat(&self);
    fn sleep(&self);
    fn as_any(&self) -> &dyn Any;
}

struct Cat {
    name: String
}
impl Cat {
    fn meow(&self)      { println!("meow") }
}
impl Animal for Cat {
    fn eat(&self)       { println!("cat {} is eating fish(or lasagne)", self.name) }
    fn sleep(&self)     { println!("cat {} sleeps inside", self.name) }
    fn as_any(&self) -> &dyn Any { self }
}

struct Lion {
    tag_id: usize
}
impl Lion {
    fn roar(&self)      { println!("roar") }
}
impl Animal for Lion {
    fn eat(&self)       { println!("lion(tag={}) is eating fish(or lasagne)", self.tag_id) }
    fn sleep(&self)     { println!("lion(tag={}) sleeps inside", self.tag_id) }
    fn as_any(&self) -> &dyn Any { self }
}

fn main() {
    let animals:Vec<Box<dyn Animal>> = vec![
                Box::new(Cat {name: "Garfield".to_string()}),
                Box::new(Lion {tag_id: 12})
    ];
    //later:
    for animal in animals.iter() {
        animal.sleep();
        if let Some(animal) = animal.as_any().downcast_ref::<Lion>() {
            animal.roar();
        }
    }
}

Aunmag
  • 852
  • 12
  • 17
0

Personally, I prefer to avoid the use of any or Box<dyn X>, prefering explicit enum wrapper. Often you get a tiny bit of boilerplate per type but I find that it does give additional type-safety and performance.

In this case I'd use an explicit enum Animal wrapping two types, struct Cat and Struct Lion that each implement a trait AnimalCore.

Then when I have an Animal and I want to use the shared behaviour I can just do animal.eat(), and if I want Lion specific behaviour I can pattern match on the enum like this: if let Animal::Lion(lion) = animal { lion.roar() }.

A complete implementation would look like this:

trait AnimalCore {
    fn eat(&self);
    fn sleep(&self);
}

struct Cat {
    name: String,
}

impl Cat {
    fn meow(&self) {
        println!("meow");
    }
}

impl AnimalCore for Cat {
    fn eat(&self) {
        println!("cat {} is eating fish(or lasagne)", self.name);
    }
    fn sleep(&self) {
        println!("cat {} sleeps inside", self.name);
    }
}

struct Lion {
    tag_id: i64,
}

impl Lion {
    fn roar(&self) {
        println!("roar");
    }
}

impl AnimalCore for Lion {
    fn eat(&self) {
        println!("lion(tag={}) is eating gazelle", self.tag_id)
    }
    fn sleep(&self) {
        println!("lion(tag={}) sleeps outside", self.tag_id)
    }
}

enum Animal {
    Cat(Cat),
    Lion(Lion),
}

impl Animal {
    fn as_core(&self) -> &dyn AnimalCore {
        match self {
            Animal::Cat(v) => v,
            Animal::Lion(v) => v,
        }
    }
    fn eat(&self) {
        self.as_core().eat()
    }
    fn sleep(&self) {
        self.as_core().sleep()
    }
}

fn main() {
    let animals = vec![
        Animal::Cat(Cat {
            name: String::from("Garfield"),
        }),
        Animal::Lion(Lion { tag_id: 12 }),
    ];

    //later:
    for animal in animals {
        animal.sleep();
        if let Animal::Lion(animal) = animal {
            animal.roar()
        }
    }
}

Which you can see on the playground

Aside: If I had lots of code that was conditional on whether the object was a lion I'd add a helper function like:

impl Animal {
  fn as_lion(&self) -> Option<&Lion> {
     match(self) { 
       Animal::Lion(lion) => Some(lion),
       _ => None
     }
  }
}

which I could then use as:

  animal.as_lion().map(Lion::roar)
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187