Simple zero cost dependency injection library made for rust
nject
Simple zero cost dependency injection library made for rust
Install
Add the following to yourCargo.toml: [dependencies] nject = "0.5" Why nject?
- Zero cost: Using this library is equivalent to manually injecting your dependencies as shown in the benchmarks.
- Compile time only: If configured incorrectly,
njectwill fail at compile time.
Use cases
Removes the need to specify dependencies across your modules
use nject::{injectable, provider};
#[injectable] struct DepOne;
#[injectable] struct DepTwo { dep: DepOne, }
#[injectable] struct Facade { dep: DepTwo, }
#[provider] struct Provider;
fn main() { let _facade: Facade = Provider.provide(); }
Works with lifetimes - enables shared dependencies
use nject::{injectable, provider};
struct DepOne;
#[injectable] struct Facade<'a> { dep: &'a DepOne, }
#[provider] struct Provider { #[provide] shared: DepOne, }
fn main() { let provider = Provider { shared: DepOne }; let _facade: Facade = provider.provide(); }
Works with dyn traits
use nject::{injectable, provider}; use std::rc::Rc;
trait Greeter { fn greet(&self); }
#[injectable] struct GreeterOne;
impl Greeter for GreeterOne { fn greet(&self) { println!("Greeting"); } }
#[injectable] struct Facade<'a> { boxed_dep: Box<dyn Greeter>, ref_dep: &'a dyn Greeter, rc_dep: Rc<dyn Greeter>, }
#[provider] #[provide(Box<dyn Greeter>, |greeter: GreeterOne| Box::new(greeter))] struct Provider { #[provide(dyn Greeter)] greeter: GreeterOne, #[provide(Rc<dyn Greeter>, |x| x.clone())] rc_greeter: Rc<GreeterOne>, }
fn main() { let provider = Provider { greeter: GreeterOne, rc_greeter: Rc::new(GreeterOne), }; let _facade: Facade = provider.provide(); }
Works with generics
use nject::{injectable, provider};
#[injectable] struct DepOne;
#[injectable] struct Facade<T> { dep: T, }
#[provider] struct Provider;
fn main() { let _facade: Facade<DepOne> = Provider.provide(); }
Works with generic providers
use nject::{injectable, provider};
trait Greeter { fn greet(&self); }
#[injectable] struct DevGreeter;
impl Greeter for DevGreeter { fn greet(&self) { println!("Greeting Dev"); } }
#[injectable] struct ProdGreeter;
impl Greeter for ProdGreeter { fn greet(&self) { println!("Greeting production"); } }
#[injectable] struct Facade<'a> { dep: &'a dyn Greeter, }
#[provider] struct Provider<'a, T: Greeter>(#[provide(dyn Greeter)] &'a T);
fn main() { let devfacade: Facade = Provider(&DevGreeter).provide(); let prodfacade: Facade = Provider(&ProdGreeter).provide(); }
Easily inject non-injectable dependencies
use nject::{inject, injectable, provider};
#[inject(Self { noninjectablevalue: 123 })] struct InjectableFromInjectAttr { noninjectablevalue: i32, }
struct NonInjectable { noninjectablevalue: i32, }
#[inject(|injectable_dep: InjectableFromInjectAttr| Self { noninjectablevalue: injectabledep.noninjectable_value + 10, injectable_dep })] struct PartiallyInjectable { noninjectablevalue: i32, injectable_dep: InjectableFromInjectAttr }
#[injectable] struct Facade { depfrominjected: InjectableFromInjectAttr, depfrompartial_inject: PartiallyInjectable, #[inject(NonInjectable { noninjectablevalue: 456 })] depfrominject_attr: NonInjectable, #[inject(InjectableFromInjectAttr { noninjectablevalue: 789 })] depfrominjectattroverride: InjectableFromInjectAttr, #[inject(|injectable_dep: InjectableFromInjectAttr| PartiallyInjectable { noninjectablevalue: 111, injectable_dep })] depfrompartialinjectattr_override: PartiallyInjectable, }
#[provider] struct Provider;
fn main() { let _facade = Provider.provide::<Facade>(); }
Use modules to export internal shared dependencies
use nject::{init, injectable, provider};
mod sub { use nject::{injectable, module}; use std::rc::Rc;
trait Greeter { fn greet(&self) -> &str; }
#[injectable] struct GreeterOne;
impl Greeter for GreeterOne { fn greet(&self) -> &str { "One" } }
#[injectable] struct InternalType(#[inject(123)] i32); // Not visible outside of module.
struct Ref<T>(Rc<T>);
#[injectable] pub struct Facade<'a> { hidden: &'a InternalType, hidden_dyn: &'a dyn Greeter, hidden_ref: Ref<InternalType>, }
#[injectable] #[module] pub struct Module { // Internal shared type exports must be made on fields (not the struct). #[export] hidden: InternalType, #[export(dyn Greeter)] hidden_dyn: GreeterOne, #[inject(|x: InternalType| Rc::new(x))] #[export(Ref<InternalType>, |x| Ref(x.clone()))] hidden_rc: Rc<InternalType>, } }
#[injectable] #[provider] struct Provider { #[import] sub_mod: crate::sub::Module, }
fn main() { let provider: Provider = init!(); let _facade = provider.provide::<sub::Facade>(); }
Limitations
- Internal dependencies can only be exported by a single module.
- Generic parameters are not supported on modules.
Use modules to export public dependencies
use nject::{init, injectable, provider};
mod sub { use nject::{injectable, module}; use std::boxed::Box; use std::rc::Rc;
pub trait Greeter { fn greet(&self) -> &str; }
#[injectable] struct GreeterOne;
impl Greeter for GreeterOne { fn greet(&self) -> &str { "One" } }
#[injectable] pub struct Facade<'a> { public_box: Box<dyn Greeter>, public_rc: Rc<dyn Greeter>, public_i32: &'a i32, }
#[injectable] #[module] // Public type exports must be made on the struct (not the fields). // To prevent name collisions, use absolute paths in types. #[export(std::boxed::Box<dyn crate::sub::Greeter>, |x: GreeterOne| Box::new(x))] #[export(std::rc::Rc<dyn crate::sub::Greeter>, self.public.clone())] #[export(&'prov i32, &123)] pub struct Module { #[inject(|x: GreeterOne| Rc::new(x))] public: Rc<dyn Greeter>, } }
#[injectable] #[provider] struct Provider { #[import] // To import module public exports, use the absolute path to its definition. sub_mod: crate::sub::Module, }
fn main() { let provider: Provider = init!(); let _facade = provider.provide::<sub::Facade>(); }
Limitations
- Generic parameters are not supported on modules.
- Module name must be unique within the same crate.
- Importing a re-exported module from an external crate is not supported. The module must be imported directly from the crate where it is defined.
Use modules to export multiple implementations for a public type
use nject::{init, injectable, provider};
mod one { use nject::{injectable, module};
#[injectable] struct GreeterOne;
impl super::Greeter for GreeterOne { fn greet(&self) -> &str { "One" } }
#[injectable] #[module] // Public type exports must be made on the struct (not the fields). // To prevent name collisions, use absolute paths in types. #[export(&'prov dyn crate::Greeter, &self.greeter)] pub struct OneModule { greeter: GreeterOne, } }
mod two { use nject::{injectable, module};
#[injectable] struct GreeterTwo;
impl super::Greeter for GreeterTwo { fn greet(&self) -> &str { "Two" } }
#[injectable] #[module] // Public type exports must be made on the struct (not the fields). // To prevent name collisions, use absolute paths in types. #[export(&'prov dyn crate::Greeter, &self.greeter)] pub struct TwoModule { greeter: GreeterTwo, } }
trait Greeter { fn greet(&self) -> &str; }
#[injectable] #[provider] struct Provider { #[import] one: crate::one::OneModule, #[import] two: crate::two::TwoModule, }
fn main() { let provider: Provider = init!(); let greetings = provider.iter::<&dyn Greeter>() .map(|g| g.greet()) .collect::<Vec<_>>(); let greeter = provider.provide::<&dyn Greeter>();
assert_eq!(greetings, vec!["One", "Two"]); assert_eq!(greeter.greet(), "Two"); }
Limitations
Same as module public exportsUse scopes to scope dependencies
use nject::{init, injectable, module, provider};
#[injectable] struct ModuleDep;
#[injectable] #[module] struct ScopeModule { #[export] module_dep: ModuleDep, }
#[injectable] struct RootDep;
#[injectable] struct ScopeDep;
#[injectable] struct ScopeFacade<'a> { root_dep: &'a RootDep, scope_dep: &'a ScopeDep, scopemoduledep: &'a ModuleDep, }
#[injectable] #[provider] #[scope(ScopeDep)] #[scope(#[import] ScopeModule)] #[scope(other: #[arg] &'scope ScopeDep)] #[scope(other: #[arg] &'scope ModuleDep)] struct Provider(#[provide] RootDep);
fn main() { let provider: Provider = init!(); let scope = provider.scope(); let scope_facade = scope.provide::<ScopeFacade>();
let otherscope = provider.otherscope(scopefacade.scopedep, scopefacade.scopemodule_dep); let otherscopefacade = otherscope.provide::<ScopeFacade>(); }
Inject providers for post-creation value injection
use nject::{injectable, provider};
#[injectable] struct Dep(#[inject(123)] i32);
#[injectable] struct Factory<'a> { dep_provider: &'a dyn nject::Provider<'a, Dep>, }
impl<'a> Factory<'a> { fn create_dep(&self) -> Dep { self.dep_provider.provide() } }
#[provider] struct Provider;
fn main() { let factory = Provider.provide::<Factory>(); let dep = factory.createdep(); }
Examples
You can look into the axum/actix example for a Web API use case or into the Leptos example for a Web App.Credits
- Syn - MIT or Apache-2.0
- Quasi-Quoting - MIT or Apache-2.0
- Rust - MIT or Apache-2.0