Building C++ like self registrering factory in Rust

I am interesting in something like described in this post C++ runtime class registration | (( meatspace ))

So I would like to have a trait like

trait ImageFormatter { ... }

And then have factory which for given extension string produces Box<ImageFormatter>.
In C++ if you implementent new ImageFormatter you can register it into factory and use (if you link it into executable). Is this possible in Rust?

use std::collections::HashMap;

trait ImageFormatter {
    fn hello(&self);
}

struct Foo;

impl ImageFormatter for Foo {
    fn hello(&self) {
        println!("foo!");
    }
}

struct Bar;

impl ImageFormatter for Bar {
    fn hello(&self) {
        println!("bar!");
    }
}

type ImageFormatterFactoryFn = fn() -> Box<ImageFormatter>;

struct Factory {
    formatters: HashMap<String, ImageFormatterFactoryFn>
}

impl Factory {
    pub fn new() -> Factory {
        Factory {
            formatters: HashMap::new()
        }
    }

    pub fn register(&mut self, extension: &str, f: ImageFormatterFactoryFn) {
        // Do something sensible if the extension was already registered?  This will
        // just replace the old key/value pair.
        self.formatters.insert(extension.to_string(), f);
    }

    pub fn create_formatter(&self, extension: &str) -> Box<ImageFormatter> {
        // Replace the unwrap() with some error checking; maybe return
        // a Result<Box<...>> instead if the wrong extension is passed
        // in.
        let f = self.formatters.get(extension).unwrap();
        f()
    }
}


fn main() {
    let mut factory = Factory::new();

    factory.register("foo", || {
        Box::new(Foo)
    });

    factory.register("bar", || {
        Box::new(Bar)
    });

    let x = factory.create_formatter("foo");
    x.hello();

    let y = factory.create_formatter("bar");
    y.hello();
}

This prints

foo!
bar!

Hopefully this will get you started.

2 Likes

Thanks, I got also almost there, but now I am stuck, since:

I would like to have a single global factory, where any new implementation can be registered (some people say that this is an anti-pattern, but you can see it in Google codebases like Tensorflow all the time).

A halfstep there is to have static factory, which you somehow initialize in main (but then you have to keep track all possible implementations). Even this needs nightly since:

static mut FORMATTER_REGISTER: Option<HashMap<&str, FormatterFactoryFn>> = None;

errors with: error[E0493]: destructors in statics are an unstable feature
Is possible to do this on stable somehow (edit: lazy_static might be the way)?

And second question. Is it possible to use build.rs to parse all the code (even dependent crates) and collect all implementations of Formatter?

You can always use the lazy_static crate for this, although I feel that's not really addressing the underlying problem (globals/singletons). A lot of the time there isn't any real need to make it a static global variable, instead users can instantiate the factory once at the very beginning and then pass it around to wherever it's needed.

You can technically do this if you want, it won't work for dependent crates though, otherwise that means an arbitrary crate lower down in the dependency graph can fiddle with something which calls it, which would be... bad.

Another thing to consider is that this is starting to sound like an X-Y problem, or trying to shoehorn OO patterns onto a functional language...

1 Like