I have various structs that all implement the same trait. I want to branch on some condition, deciding at runtime which of those structs to instantiate. Then, regardless of which branch I followed, I want to call methods from that trait.
Is this possible in Rust? I'm hoping to achieve something like the following (which does not compile):
trait Barks {
fn bark(&self);
}
struct Dog;
impl Barks for Dog {
fn bark(&self) {
println!("Yip.");
}
}
struct Wolf;
impl Barks for Wolf {
fn bark(&self) {
println!("WOOF!");
}
}
fn main() {
let animal: Barks;
if 1 == 2 {
animal = Dog;
} else {
animal = Wolf;
}
animal.bark();
}
Yes, but not that easily. What you've written there is that
animal
should be a variable of typeBarks
, butBarks
is a trait; a description of an interface. Traits don't have a statically-defined size, since a type of any size could come along andimpl Barks
. The compiler has no idea how big to makeanimal
.What you need to do is add a layer of indirection. In this case, you can use
Box
, although you can also use things likeRc
or plain references:Here, I'm allocating the
Dog
orWolf
on the heap, then casting that up to aBox<Barks>
. This is kind of like casting an object to an interface in something like C# or Java, or casting aDog*
to aBarks*
in C++.An entirely different approach you could also use would be enums. You could have
enum Animal { Dog, Wolf }
then define animpl Animal { fn bark(&self) { ... } }
. Depends on whether you need a completely open-ended set of animals and/or multiple traits.Finally, note that "kind of" above. There are various things that don't work as they would in Java/C#/C++. For example, Rust doesn't have downcasting (you can't go from
Box<Barks>
back toBox<Dog>
, or from one trait to another). Also, this only works if the trait is "object safe" (no generics, no usingself
orSelf
by-value).DK has a good explanation, I'll just chime in with an example where we allocate the
Dog
orWolf
on the stack, avoiding a heap allocation:It's a bit ugly, but the references accomplish the same indirection as a
Box
with a smidge less overhead.Defining a custom enumeration is the most efficient way to do this. This will allow you to allocate on the stack exactly the amount of space you need, i.e. the size of the largest option, plus 1 extra byte to track which option is stored. It also allows direct access without a level of indirection, unlike solutions using a
Box
or a trait reference.Unfortunately, it does require more boiler-plate:
In
main
we use only a single stack allocated variable, holding an instance of our custom enumeration.