Attribute Macro lockjaw::define_component[][src]

#[define_component]
Expand description

Generates a #[component] at the root of the binary.

If a component is annotated with #[define_component] instead of the regular #[component] attribute, instead of having to list all #[module] lockjaw will gather every #[module] with the install_in metadata from the binary’s dependencies, and automatically install them in the component.

All other operations are identical to a regular #[component], and the modules metadata can still be used.

The main advantage of using #[define_component] is inverting the dependency between a client and a framework. With regular #[component] a framework must depend on the client since it has to reference the client module for extensions, and the implementation has to be generated when the framework is compiled. When #[define_component] is used, the framework merely defines the component and adds its bindings to it, while the client will complete the dependency graph and the implementation is automatically generated.

For subcomponents use #[define_sumcomponent] instead.


// The component can be defined by other crates.
#[define_component]
trait MyComponent{}

struct MyModule {}

#[module(install_in: MyComponent)]
impl MyModule {
    #[provides]
    pub fn provide_i(&self) -> i32 {
        42
    }
}

#[entry_point(install_in: MyComponent)]
pub trait MyEntryPoint {
    fn i(&self) -> i32;
}

pub fn main() {
    let component: Box<dyn MyComponent> = <dyn MyComponent>::new();

    assert_eq!(<dyn MyEntryPoint>::get(component.as_ref()).i(), 42)
}

lockjaw::epilogue!(root);

Root crate

Using #[define_component]/#[define_subcomponent] requires a root crate, which is done by passing root to the epilogue!() macro. Typically this is done on a binary.

A root crate generates the implementation for the component, hence preventing any further extensions to the dependency graph. Lockjaw will prevent any other crates using lockjaw from depending directly or indirectly on a root crate.

Entry points

A regular #[component] requires all external access to the component to be done through the component trait, which means the component user has to indirectly depend on all types the component provides

When #[define_component], lockjaw can also provide access to a subset of the dependency graph by using #[entry_point]. #[entry_point] can take an opaque component trait and safely access bindings from it(with compile time check). Users of the component can only require bindings they need directly, without having to know about other things the component provides.