Crate lockjaw[−][src]
Expand description
Lockjaw
Lockjaw is a fully static, compile-time dependency injection framework for Rust inspired by Dagger. It is also what you get when jabbed by a rusty dagger.
Features:
- Compile time dependency graph validation with helpful diagnostic messages. The code won’t compile if a dependency is missing or there are cyclic dependencies.
- Cross-crate injection. Libraries can provide different implementations like prod and test, and allow clients to choose from them.
- Aims for feature parity with Dagger. If you have used Dagger before, lockjaw should feel familiar.
- Implemented:
@Inject
=>#[inject]
constructor injection in#[injectable]
@Provides
=>#[provides]
bind method return values@Binds
=>#[binds]
bind trait to implementation.@Singleton
/@Scope
=>scope=component
shared instance.@Named
=>#[qualified]
Provider<T>
=>Provider<T>
create multiple instances at run time.Lazy<T>
=>Lazy<T>
create and cache instance only when used.- Subcomponents
=>
#[subcomponent]
Dynamically creatable sub-scopes with additional bindings - Multibindings
=>
#[into_vec]
/#[into_map]
Collect same bindings to a Vec/HashMap, useful for plugin systems. @BindsOptionalOf
=>#[binds_option_of]
Allow some bindings to be missing- Factories
=>
#[facotry]
create objects with both injected fields and runtime fields. - Hilt
=>
#[define_component]
/#[entry_point
/install_in
Automatic module collection from build dependency.
- Todo
- Producers async dependency injection. Might not be too useful comparing to async/await
- Implemented:
See user guide for more information.
Example:
use lockjaw::*; use std::ops::Add; lockjaw::prologue!("src/lib.rs"); struct GreetCounter { counter: ::std::cell::RefCell<i32> } // Allow GreetCounter to be created in the dependency graph. These bindings are available anywhere. #[injectable] impl GreetCounter { // Marks a method as the inject constructor. Lockjaw will call this to create the object. #[inject] pub fn new() -> Self { Self{counter : std::cell::RefCell::new(0) } } } impl GreetCounter{ pub fn increment(&self) -> i32 { let mut m = self.counter.borrow_mut(); *m = m.add(1); m.clone() } } pub trait Greeter { fn greet(&self) -> String; } struct GreeterImpl { greet_counter : crate::GreetCounter, phrase : String } #[injectable] impl GreeterImpl { // Lockjaw will call this with other injectable objects provided. #[inject] pub fn new(greet_counter : GreetCounter, phrase : String) -> Self { Self { greet_counter, phrase } } } impl Greeter for GreeterImpl{ fn greet(&self) -> String{ format!("{} {}", self.phrase, self.greet_counter.increment()) } } // Declare a module so we can do special bindings. These bindings are only available if the // component installs the module, so different bindings can be used based on the situation. struct MyModule {} #[module] impl MyModule { // When ever someone needs a Greeter, use GreeterImpl as the actual implementation #[binds] pub fn bind_greeter(_impl : crate::GreeterImpl) -> Cl<dyn Greeter> {} // Called when a String is requested #[provides] pub fn provide_string() -> String { "helloworld".to_owned() } } // Components stitch modules and injectables together into a dependency graph, and can create // objects in the graph. The component installs modules listed in `modules` #[component(modules: MyModule)] trait MyComponent { // Allows creating a greeter with the component. The created object has the lifetime of the // component fn greeter(&self) -> Cl<dyn Greeter>; } pub fn main() { // Creates the component let component = MyComponent::new(); // Creates a greeter. let greeter = component.greeter(); assert_eq!(greeter.greet(), "helloworld 1"); // Internal states of the greeter is kept. assert_eq!(greeter.greet(), "helloworld 2"); // A new greeter has a new independent set of injected objects. assert_eq!(component.greeter().greet(), "helloworld 1"); } // called after the last use of lockjaw to perform validation and code generation epilogue!();
Disclaimer
This is not an officially supported Google product.
Lockjaw is currently in early development and all APIs are subjected to changes. Some feature are also implemented in a hacky way. Use at your own risk.
Modules
Additional nested attributes for items under a #[component]
Additional nested attributes for items under a #[injectable]
Additional nested attributes for items under a #[module]
Macros
Resolves the dependency graph and generate component code. Must be called in in the crate root
(lib.rs
or main.rs
), after any other lockjaw macros, and outside any mod
/functions
Prepares lockjaw to be used in the current file. Must be called in every file that uses any lockjaw attribute macros, and before such usage.
Structs
Enums
“Component Lifetime”. Wrapper around an injection that may be scoped(owned by the component) or free standing(owned by the item injecting it). Deref to access the content.
Traits
Functions
Function that must be called inside the cargo build script to setup the lockjaw environment.
Attribute Macros
Annotates a struct that contains modules
instances to be installed in a
component
with the builder_modules
field. If a module contains fields, it cannot be
auto generated and must be explicitly provided to
COMPONENT.build()
Annotates a trait that composes the dependency graph and provides items in the graph (An “injector”) .
Annotates a non-public [injectable]
struct, a #[module]
struct, or a
trait used by components so their implementation can be generated.
Generates a #[component]
at the root of the binary.
Generates a #[subcomponent]
at the root of the binary, similar
to #[define_component]
Annotates a trait to provide bindings access to an opaque #[define_component]
/#[define_subcomponent]
component.
Annotates a struct impl that can be provided to the dependency graph.
Annotates a impl block that defines the bindings.
Annotates a struct to declare a binding qualifier.
Annotates a trait that composes a sub-dependency graph, which has additional bindings/scoped
bindings, and can also access bindings from the parent #[component]
/
#[subcomponent]
.