๐งฉ 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.