nicolascotton
nject
Rust

Simple zero cost dependency injection library made for rust

Last updated Jul 1, 2026
87
Stars
7
Forks
4
Issues
+3
Stars/day
Attention Score
41
Language breakdown
Rust 100.0%
โ–ธ Files click to expand
README

nject

Crates.io version Download docs.rs docs

Simple zero cost dependency injection library made for rust

Install

Add the following to your Cargo.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, nject will 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 exports

Use 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

๐Ÿ”— More in this category

ยฉ 2026 GitRepoTrend ยท nicolascotton/nject ยท Updated daily from GitHub