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();
}
A variable is called polymorphic if it refers to different values under different conditions. Object variables (instance variables) represent the behavior of polymorphic variables in Java. It is because object variables of a class can refer to objects of its class as well as objects of its subclasses.
A polymorphic variable is a variable that can hold values of different types during the course of execution.
In object-oriented programming, polymorphism (from the Greek meaning "having multiple forms") is the characteristic of being able to assign a different meaning or usage to something in different contexts - specifically, to allow an entity such as a variable, a function, or an object to have more than one form.
Polymorphism allows us to perform a single action in different ways. In other words, polymorphism allows you to define one interface and have multiple implementations. The word “poly” means many and “morphs” means forms, So it means many forms.
Yes, but not that easily. What you've written there is that animal
should be a variable of type Barks
, but Barks
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 and impl Barks
. The compiler has no idea how big to make animal
.
What you need to do is add a layer of indirection. In this case, you can use Box
, although you can also use things like Rc
or plain references:
fn main() {
let animal: Box<dyn Barks>;
if 1 == 2 {
animal = Box::new(Dog);
} else {
animal = Box::new(Wolf);
}
animal.bark();
}
Here, I'm allocating the Dog
or Wolf
on the heap, then casting that up to a Box<dyn Barks>
. This is kind of like casting an object to an interface in something like C# or Java, or casting a Dog*
to a Barks*
in C++.
An entirely different approach you could also use would be enums. You could have enum Animal { Dog, Wolf }
then define an impl 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<dyn Barks>
back to Box<Dog>
, or from one trait to another). Also, this only works if the trait is "object safe" (no generics, no using self
or Self
by-value).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With