Builder Modules

In the previous chapter the modules are without fields, so Lockjaw can easily create instances of it (In fact no instance are created since Lockjaw can just call the static methods). However sometimes we want to be able to affect the values that are bound at runtime, hence need to be able to change what #[provides] does using fields in the module.

struct MyModule {
    i: i32,
}

#[module]
impl MyModule {
    #[provides]
    pub fn provide_i32(&self) -> i32 {
        self.i
    }
}

Since the struct now has fields Lockjaw can no longer automatically create it, the user must manually pass in the modules when creating the component.

Implementation note: While Lockjaw can also try to use Default or some other mechanisms, usages like this implies the module has mutable state and generally is a bad idea.

Using builder modules

Instead of passing the runtime modules to the component one by one, they are collected in a single struct annotated by the #[builder_modules] attribute.

#[builder_modules]
struct MyBuilderModules {
    my_module: MyModule,
}

Every field in the struct should be a module. Using a struct makes sure each module will be required by the compiler/IDE while exposing the least amount of generated code(which is harder for users and IDEs to understand, it is better to spell everything out in visible code.).

The #[builder_modules] can then be installed in the component using the builder_modules metadata

#[component(builder_modules: MyBuilderModules)]
trait MyComponent {
    fn i32(&self) -> i32;
}

The component only accepts one#[builder_modules], which is likely to be specifically tailored for the component. The modules itself can be shared.

The builder_modules metadata can be used at the same time with the modules metadata. modules should be preferred whenever possible as they are easier to use.

Creating components with builder modules

If the builder_modules metadata is specified, the #[builder_modules] struct will become the parameter for the build() method of the component which the user must pass.

#[test]
fn test() {
    let my_module = MyModule { i: 42 };
    let my_builder_modules = MyBuilderModules { my_module };
    let component: Box<dyn MyComponent> = <dyn MyComponent>::build(my_builder_modules);
    assert_eq!(component.i32(), 42);

    let other_component: Box<dyn MyComponent> = <dyn MyComponent>::build(MyBuilderModules {
        my_module: MyModule { i: 123 },
    });
    assert_eq!(other_component.i32(), 123);
}

Source of this chapter