Is it possible to pass a reference to an uninitialised struct?

I want to create an array of unitialised structs based on a specific trait so that I can do something progromatically.

But I cant work out how to do it or if its possible.

trait Foo {
    const NAME: &'static str;
    fn new() -> Self;
    fn name(&self) -> &'static str {
        Self::NAME
    }
}

struct Bar {}

impl Foo for Bar {
    const NAME: &'static str = "Bar";

    fn new() -> Self {
        Self {}
    }
}

struct Baz {}

impl Foo for Baz {
    const NAME: &'static str = "Baz";

    fn new() -> Self {
        Self {}
    }
}

fn main() {
    let structs = [Bar, Baz];
    for s in structs{
        let inst = s::new();
        println!("{}", inst.name())
    }
}

(Playground)

What you are trying to achieve is possible, but you might need to structure it slightly differently.

The code you've written tries to treat a struct's name as some sort of object that can be passed around and used to construct the type dynamically, similar to the java.lang.Class in Java or the name of a class in Python.

Rust deliberately doesn't have a reflection system which knows how to construct objects at runtime, so we kinda need to implement it ourselves. This isn't as hard as it sounds.

What I would do is store a list of functions that can be used to construct these values, and make sure the function returns a value who's type is erased at runtime. Normally you can't store a Baz and a Bar in the same slice because they have different types with possibly different sizes, so it's important to add some level of indirection to "forget" the actual size/type of Bar and Baz (i.e. Box<dyn Foo>).

First, let's modify the Foo trait so it is "object safe". In order to erase the type at runtime, your trait can't have any methods which would require knowing a value's actual type, so things like associated constants/types (they're associated with your type) or methods that accept/return Self by value (the compiler would need to know the value's type in order to allocate the right amount of stack space for variables) are out.

trait Foo {
  fn name(&self) -> &'static str;
}

struct Bar;
impl Foo for Bar { fn name(&self) -> &'static str { "Bar" } }

struct Baz;
impl Foo for Baz { fn name(&self) -> &'static str { "Baz" } }

Next, we add our constructor functions to the structs array.

type FooConstructor = fn() -> Box<dyn Foo>;

let structs: [FooConstructor; 2] = [
    || Box::new(Bar),
    || Box::new(Baz),
];

And now we can iterate through the constructors, and create instances of our Foo type.

for s in structs {
    let instance: Box<dyn Foo> = s();
    println!("{}", instance.name());
}

(playground)

This way of passing around a closure to "defer" an object's construction can be particularly useful when you are making some sort of "factory" that will pick and choose which Foo implementation to construct at runtime.

3 Likes

This has helped me a lot thank you! I was thinking something along the lines of Box might need to be used, but I had no idea how....

I cut my teeth on Python, so that probably explains my thinking!

1 Like