Of all the recipes in this chapter, this one is probably the most stable, as it is being considered for immediate stabilization. The discussion can be found at https://github.com/rust-lang/rust/issues/34511.
While you can use abstract return types for some forms of dependency inversion, you cannot use them for traditional Java-style factories that return different objects depending on a parameter. This is because abstract return types only hide the specific struct returned on the outside of the function, but still internally rely on a specific return value. Because of this, the following code will not compile:
trait Animal {
fn do_sound(&self);
}
struct Dog;
impl Animal for Dog {
fn do_sound(&self) {
println!("Woof");
}
}
struct Cat;
impl Animal for Cat {
fn do_sound(&self) {
println!("Meow");
}
}
enum AnimalType {
Dog,
Cat,
}
fn create_animal(animal_type: AnimalType) -> impl Animal {
match animal_type {
AnimalType::Cat => Cat {},
AnimalType::Dog => Dog {},
}
}
While the outside world doesn't know which animal is going to be returned by create_animal, the function itself needs a specific return type internally. Because the first possible return from our match is an instance of Cat, create_animal assumes that we are going to return no other type. We break that expectation in the next line by returning a Dog, so the compiler fails:
If we want this factory to compile, we need to resort back to Box again:
fn create_animal(animal_type: AnimalType) -> Box<Animal> {
match animal_type {
AnimalType::Cat => Box::new(Cat {}),
AnimalType::Dog => Box::new(Dog {}),
}
}
For most purposes, you won't need a factory such as the one presented here. It is considered more idiomatic to use generics with trait bounds.