Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are polymorphic variables allowed?

Tags:

rust

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();
}
like image 872
rlkw1024 Avatar asked Jan 29 '15 16:01

rlkw1024


People also ask

What is a polymorphic variable?

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.

Does polymorphism apply to variables?

A polymorphic variable is a variable that can hold values of different types during the course of execution.

What is the concept of polymorphism?

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.

Why do we use polymorphism?

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.


1 Answers

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).

like image 171
DK. Avatar answered Sep 23 '22 01:09

DK.