2

I'm trying to write a function in rust that builds different types of objects (all implementations of a same trait) but I'm running into troubles, not understanding if I'm trying to do something illegal or just not grasping correct syntax. Here is a toy sample, not compiling (neither of the three variants of animal_factory function compiles):

enum Error {
    UnknownAnimal,
}

trait Animal {
    fn bark(&self);
}

struct Dog {}

impl Dog {
    fn new() -> Result<Dog, Error> {
        Ok(Dog {})
    }
}

impl Animal for Dog {
    fn bark(&self) {
        println!("Bau! Bau!");
    }
}

struct Cat {}

impl Cat {
    fn new() -> Result<Cat, Error> {
        Ok(Cat {})
    }
}

impl Animal for Cat {
    fn bark(&self) {
        println!("Meow! Meow!");
    }
}

/*
fn animal_factory(kind: &str) -> Result<impl Animal, Error> {
    match kind {
        "cat" => Cat::new(),
        "dog" => Dog::new(),
        _ => Err(Error::UnknownAnimal),
    }
}
*/

/*
fn animal_factory(kind: &str) -> Result<impl Animal, Error> {
    match kind {
        "cat" => {
            return Cat::new();
        },
        "dog" => {
            return Dog::new();
        },
        _ => {
            return Err(Error::UnknownAnimal);
        },
    }
}
*/

fn animal_factory(kind: &str) -> Result<impl Animal, Error> {
    if kind == "cat" {
        Cat::new()
    } else if kind == "dog" {
        Dog::new()
    } else {
        Err(Error::UnknownAnimal)
    }
}

fn main() {
    let x = animal_factory("cat").unwrap();
    let y = animal_factory("dog").unwrap();
}

Any insights? maybe I need to Box things up?

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
Pyper
  • 23
  • 4
  • I recommend you read [this](https://stackoverflow.com/a/74974361/15602349) description of trait objects. It should help you understand them on a deeper level. – at54321 Apr 21 '23 at 15:07

1 Answers1

2

impl Animal won't work if you try to return different concrete types (Cat and Dog in your case), even if both implement Animal. From the docs:

These types stand in for another concrete type where the caller may only use the methods declared by the specified Trait.

So trying to return either Cat or Dog is not going to work with impl Animal as your return type, due to a type mismatch between these two concrete types.

Luckily we can easily create one common type from both Cat and Dog instances that also implements Animal: trait objects. Trait objects are annotated with dyn Animal, instead of impl Animal. Trait objects are dynamically sized so we need to wrap them into a Box or other kind of (smart) pointer or reference:

fn animal_factory(kind: &str) -> Result<Box<dyn Animal>, Error> {
    if kind == "cat" {
        Ok(Box::new(Cat::new()?))
    } else if kind == "dog" {
        Ok(Box::new(Dog::new()?))
    } else {
        Err(Error::UnknownAnimal)
    }
}

Playground.

cafce25
  • 15,907
  • 4
  • 25
  • 31
Jonas Fassbender
  • 2,371
  • 1
  • 3
  • 19