how to implement abstract factory in rust?

Viewed 1457

Something like:

trait Thingy {
    fn hallo(&self);
}

trait Factory {
    fn make(&self) -> Thingy;
}
//------------------------------
struct AThingy {}

impl Thingy for AThingy {
    fn hallo(&self) {
        println!("i'm A thingy");
    }
}

struct AFactory {}

impl Factory for AFactory {
    fn make(&self) -> AThingy {
        AThingy{}
    }
}

//------------------------------
struct BThingy {}

impl Thingy for BThingy {
    fn hallo(&self) {
        println!("i'm B thingy");
    }
}

struct BFactory {}

impl Factory for BFactory {
    fn make(&self) -> BThingy {
        BThingy{}
    }
}

//------------------------------
#[test]
fn test_factory() {
    let aFactory:Factory = AFactory{};
    let bFactory:Factory = BFactory{};
    
    aFactory.make().hallo();
    bFactory.make().hallo();
}

tried to attach Sized at various places but all fails.

2 Answers

You can use associated types. Factory can have an associated type called Output. You can add a bound that requires Output to implement Thingy:

trait Factory {
    type Output: Thingy;
    
    fn make(&self) -> Self::Output;
}

Now, AFactory's Output will be AThingy:

impl Factory for AFactory {
    type Output = AThingy;
    
    fn make(&self) -> AThingy {
        AThingy {}
    }
}

And BFactory's Output will be BThingy:

impl Factory for BFactory {
    type Output = BThingy;
    
    fn make(&self) -> BThingy {
        BThingy {}
    }
}

As @PeterHall mentioned, you cannot deal with unsized types in Rust, so to store a Factory you need to use an owned pointer like Box<dyn Factory>:

#[test]
fn test_factory() {
    let aFactory: Box<dyn Factory> = Box::new(AFactory {});
    let bFactory: Box<dyn Factory> = Box::new(BFactory {});

    aFactory.make().hallo();
    bFactory.make().hallo();
}

However, because Factory has an associated type, you also have to specify the Output when making it into a trait object:

#[test]
fn test_factory() {
    let aFactory: Box<dyn Factory<Output = AThingy>> = AFactory {};
    let bFactory: Box<dyn Factory<Output = BThingy>> = BFactory {};

    aFactory.make().hallo();
    bFactory.make().hallo();
}

When Thingy is a trait, this:

fn make(&self) -> Thingy;

is equivalent to:

fn make(&self) -> dyn Thingy;

That is, an unsized bare trait object. You cannot deal with unsized types like that in Rust; trait objects need to be behind a pointer of some kind. Given that the function creates the object, it can't be a &dyn Thingy, so you'll need an owned pointer, like Box<dyn Thingy>.

So your Factory trait will look like this:

trait Factory {
    fn make(&self) -> Box<dyn Thingy>;
}

And implementations look like this:

impl Factory for AFactory {
    fn make(&self) -> Box<dyn Thingy> {
        Box::from(AThingy{})
    }
}

Given your question, I would guess you are trying to apply patterns that you are used to from object-oriented languages, like Java. These patterns go against the idioms that are commonly used in Rust, and force you to box data, that probably wouldn't otherwise need to be boxed. In Java, almost everything generic is boxed, and you pay for that.

Related