How can I store a type in an array?

Viewed 307

I would like to create an array containing struct types (non-instanced), all implementing a same trait. I have tried to do this:

trait TraitA {
    fn new(number: i16) -> Self;
    fn get_name() -> &'static str;
}

struct StructA {
    bar: u8
}
struct StructB {
    foo: i16
}

impl TraitA for StructA {
    fn new(number: i16) -> Self {
        StructA { bar: number as u8 }
    }
    fn get_name() -> &'static str
    { "StructA" }
}
impl TraitA for StructB {
    fn new(number: i16) -> Self {
        StructB { foo: number }
    }
    fn get_name() -> &'static str
    { "StructB" }
}

fn main() {
    let struct_array = [StructA, StructB];

    for i in 0..struct_array.len() {
        println!("{}", struct_array[i]::get_name());
        struct_array[i]::new(i);
    }
}

The compiler expects an actual value, not a type.

In Python, I would do as such:

class ClassA:
    def __init__(self, number):
        self.bar = number
    def get_name():
        return "ClassA"
class ClassB:
    def __init__(self, number):
        self.foo = number
    def get_name():
        return "ClassB"

if __name__ == "__main__":
    class_array = [ClassA, ClassB]
    for i in range(2):    
        print(class_array[i].get_name())
        class_array[i](i)

How could I achieve this ?

1 Answers

In Python, classes are also objects and callables, and calling them you create a new object of that type.

In Rust, as SvenMarnach commented above, types are not objects; they only exist at compile time and have no representation in the running program.

To do what you want you have to create a type that imitates the Python class type. Some kind of factory pattern, that will naturally be a trait. I'm assuming that you want to do something with those TraitA objects, other than creating them, so let's add something useful and move the building stuff to another trait:

trait TraitA {
    fn do_something(&self);
}
trait FactoryA {
    fn new(&self, number: i16) -> Box<dyn TraitA>;
    fn get_name(&self) -> &'static str;
}

Note that I'm adding a &self argument to every function. That is necessary because we'll want to invoke dynamic dispatching later, and in Rust you cannot have dynamic dispatch without self. But this trait represents your Python classes, so this member functions are analogous to Python class methods.

Also the new function could return an associated type, but that will not play well with the dynamic dispatching either, so I'm returning a type erased Box<dyn TraitA>.

Now, implementing a couple of classes is quite boring:

struct StructA {
    bar: u8
}
struct StructB {
    foo: i16
}

impl TraitA for StructA {
    fn do_something(&self) {
        println!("I'm an A({})", self.bar);
    }
}
impl TraitA for StructB {
    fn do_something(&self) {
        println!("I'm a B({})", self.foo);
    }
}

Implementing the factories is more interesting:

struct BuilderA;

struct BuilderB;

impl FactoryA for BuilderA {
    fn new(&self, number: i16) -> Box<dyn TraitA> {
        Box::new(StructA { bar: number as u8 })
    }
    fn get_name(&self) -> &'static str
    { "StructA" }
}
impl FactoryA for BuilderB {
    fn new(&self, number: i16) -> Box<dyn TraitA> {
        Box::new(StructB { foo: number })
    }
    fn get_name(&self) -> &'static str
    { "StructB" }
}

We use zero sized types (ZST) for the builders, because we have nothing to store there.

The main function is also quite straightforward. I've switched your loop into a Iterator::enumerate just for fun:

fn main() {
    let struct_array: Vec<Box<dyn FactoryA>> = vec![
        Box::new(BuilderA),
        Box::new(BuilderB),
    ];

    for (i, b) in struct_array.iter().enumerate() {
        println!("{}", b.get_name());
        let a = b.new(i as i16);
        a.do_something();
    }
}

This works as expected and prints (playground):

StructA
I'm an A(0)
StructB
I'm a B(1)
Related