π§© di β Dependency Injection for Rust
A lightweight, async-friendly, scoped dependency injection container for Rust
β¨ Features
- β Singleton / Scoped / Transient lifetimes
- β Named service instances
- β Async factory support
- β Circular dependency detection
- β Procedural macro for registration
- β Task-local scope isolation
- β
Thread-safe with
Arc
+RwLock
π Quick Start
1. Add to Cargo.toml
[]
= { = "rust_di", = "" }
= "0.4" # Required for automatic handler & pipeline registration
Why ctor
?
di
uses the ctor
crate to automatically register services at startup. Without it, nothing will be
wired up.
2. Register services
;
3. Resolve services
async
π Automatic DI Scope Initialization - #[di::with_di_scope]
The #[di::with_di_scope] macro wraps an async fn in DIScope::run_with_scope(...), automatically initializing the task-local context required for resolving dependencies.
β Example: Replacing main
You can replace the entire DIScope::run_with_scope
block in your main function with a simple macro
:
use ;
async
π§ This macro fully replaces the manual block shown in section 3. Resolve services.
π Example: Background queue consumer loop
use ;
use ;
async
async
This pattern is ideal for long-running background tasks, workers, or event handlers that need access to scoped services.
β Why use #[with_di_scope]?
- Eliminates boilerplate around
DIScope::run_with_scope
- Ensures
task-local
variables are properly initialized - Works seamlessly in
main
,background loops
, or anyasync entrypoint
- Encourages
clean
, scoped service resolution
π§ Lifetimes
Lifetime | Behavior |
---|---|
Singleton | One instance per app |
Scoped | One instance per DIScope::run_with_scope |
Transient | New instance every time |
π§° Procedural Macro
Use #[di::registry(...)]
to register services declaratively:
Supports:
- Singleton, Scoped, Transient
- factory β use
DiFactory
orcustom factory
- name = "..." β register named instance
π§ͺ Testing
cargo test-default
Covers:
- Singleton caching
- Scoped reuse
- Transient instantiation
- Named resolution
- Circular dependency detection
π Safety
- All services are stored as
Arc<RwLock<T>>
- Internally uses
DashMap
,ArcSwap
, andOnceCell
- Task-local isolation via
tokio::task_local!
β οΈ Limitation: tokio::spawn
drops DI context
Because DIScope
relies on task-local
variables (tokio::task_local!
), spawning a new task with tokio::spawn
will lose the current DI scope context.
;
spawn
β Workaround
If you need to spawn a task that uses DI, wrap the task in a new scope:
;
spawn
Alternatively, pass the resolved dependencies into the task before spawning.
#StandForUkraine πΊπ¦
This project aims to show support for Ukraine and its people amidst a war that has been ongoing since 2014. This war has a genocidal nature and has led to the deaths of thousands, injuries to millions, and significant property damage. We believe that the international community should focus on supporting Ukraine and ensuring security and freedom for its people.
Join us and show your support using the hashtag #StandForUkraine. Together, we can help bring attention to the issues faced by Ukraine and provide aid.