I've been playing with what I call a second-order factory pattern, i.e., a factory which creates another factory. However, I appear to be stuck due to a lifetime problem I do not understand. The "final" factory (the "production chain" in my example) returns an Output
with a lifetime of 'static
(ie, owned data), which does not prevent Rust from giving me the good old E0515
: "cannot return value referencing local variable materials
".
So:
- I would like to understand what is going on
- I would like to convince the compiler that no, the output does not, in fact, reference anything since it's owned data
- I would also like to know if there is a better/simpler way of architecturing the code (ie, is
Buildable
really necessary here?)
Here we go:
use async_trait::async_trait;
// Something which can produce raw materials
struct Mine;
impl Mine {
fn extract_materials(&self) -> RawMaterials {
RawMaterials
}
}
// Raw materials to produce all widgets
struct RawMaterials;
// Available widgets
struct BlueWidget {
engraving: String,
}
struct RedWidget {
engraving: String,
}
// A way to customize widgets with a specific engraving
trait Blueprints {
fn engraving(&self) -> String;
}
struct BlueWidgetBlueprints {
engraving: String,
}
impl Blueprints for BlueWidgetBlueprints {
fn engraving(&self) -> String {
self.engraving.clone()
}
}
struct RedWidgetBlueprints {
engraving: String,
}
impl Blueprints for RedWidgetBlueprints {
fn engraving(&self) -> String {
self.engraving.clone()
}
}
// A container for the mine.
struct Builder {
mine: Mine,
}
trait Buildable<'a>: Blueprints {
type Factory: Factory<'a, Self::Output, Blueprints = Self>;
type Output: 'static + Send + Sync;
}
// A factory for widgets. Factories need different production chains for different widget types. A
// factory can setup production types for multiple widget types.
struct WidgetFactory<'a> {
materials: &'a mut RawMaterials,
}
#[async_trait]
trait Factory<'a, Output: 'static> {
type Blueprints: Blueprints;
type ProductionChain<'b>: ProductionChain<'b, Output>
where
Self: 'b;
async fn new(materials: &'a mut RawMaterials) -> Self;
async fn setup_production_chain<'b>(
&'b mut self,
blueprints: Self::Blueprints,
) -> Self::ProductionChain<'b>;
}
#[async_trait]
impl<'a> Factory<'a, BlueWidget> for WidgetFactory<'a> {
type Blueprints = BlueWidgetBlueprints;
type ProductionChain<'b> = BlueWidgetProductionChain<'b> where Self: 'b;
async fn new(materials: &'a mut RawMaterials) -> Self {
Self { materials }
}
async fn setup_production_chain<'b>(
&'b mut self,
blueprints: Self::Blueprints,
) -> BlueWidgetProductionChain<'b> {
BlueWidgetProductionChain {
materials: self.materials,
blueprints,
}
}
}
#[async_trait]
impl<'a> Factory<'a, RedWidget> for WidgetFactory<'a> {
type Blueprints = RedWidgetBlueprints;
type ProductionChain<'b> = RedWidgetProductionChain<'b> where Self: 'b;
async fn new(materials: &'a mut RawMaterials) -> Self {
Self { materials }
}
async fn setup_production_chain<'b>(
&'b mut self,
blueprints: Self::Blueprints,
) -> RedWidgetProductionChain<'b> {
RedWidgetProductionChain {
materials: self.materials,
blueprints,
}
}
}
// Production chains for widgets. Production chains actually produce a widget, based on a set of
// blueprints.
struct BlueWidgetProductionChain<'a> {
materials: &'a mut RawMaterials,
blueprints: BlueWidgetBlueprints,
}
struct RedWidgetProductionChain<'a> {
materials: &'a mut RawMaterials,
blueprints: RedWidgetBlueprints,
}
#[async_trait]
trait ProductionChain<'a, Output: 'static> {
async fn build(&mut self) -> Output;
}
#[async_trait]
impl<'a> ProductionChain<'a, BlueWidget> for BlueWidgetProductionChain<'a> {
async fn build(&mut self) -> BlueWidget {
BlueWidget {
engraving: self.blueprints.engraving(),
}
}
}
#[async_trait]
impl<'a> ProductionChain<'a, RedWidget> for RedWidgetProductionChain<'a> {
async fn build(&mut self) -> RedWidget {
RedWidget {
engraving: self.blueprints.engraving(),
}
}
}
impl<'a> Buildable<'a> for BlueWidgetBlueprints {
type Factory = WidgetFactory<'a>;
type Output = BlueWidget;
}
impl<'a> Buildable<'a> for RedWidgetBlueprints {
type Factory = WidgetFactory<'a>;
type Output = RedWidget;
}
impl Builder {
async fn build_widget<B>(&self, blueprints: B) -> <B as Buildable>::Output
where
B: for<'t> Buildable<'t>,
{
let mut materials = self.mine.extract_materials();
let mut factory = B::Factory::new(&mut materials).await;
let mut production_chain = factory.setup_production_chain(blueprints).await;
let result = production_chain.build().await;
// This compiles fine when replacing `result` with `todo!()`
result
}
}
fn main() {
let builder = Builder { mine: Mine };
let _widget = builder.build_widget(BlueWidgetBlueprints {
engraving: "Your first name here".to_string(),
});
let _widget = builder.build_widget(RedWidgetBlueprints {
engraving: "Your last name here".to_string(),
});
}