| Crates.io | dependency-injector |
| lib.rs | dependency-injector |
| version | 0.2.2 |
| created_at | 2025-12-14 01:45:31.240349+00 |
| updated_at | 2025-12-29 01:30:41.979978+00 |
| description | High-performance, lock-free dependency injection container for Rust |
| homepage | |
| repository | https://github.com/pegasusheavy/dependency-injector |
| max_upload_size | |
| id | 1983631 |
| size | 240,010 |
A high-performance, lock-free dependency injection container for Rust.
DashMap for concurrent access without mutex contentionArc<Container>Add to your Cargo.toml:
[dependencies]
dependency-injector = "0.2"
[dependencies]
dependency-injector = { version = "0.2", features = ["logging-json"] }
| Feature | Description |
|---|---|
logging |
Basic debug logging with tracing crate (enabled by default) |
logging-json |
JSON structured logging output (recommended for production) |
logging-pretty |
Colorful pretty logging output (recommended for development) |
async |
Async support with Tokio |
derive |
Compile-time dependency injection with #[derive(Inject)] |
use dependency_injector::Container;
// Define your services
#[derive(Clone)]
struct Database {
url: String,
}
#[derive(Clone)]
struct UserService {
db: Database,
}
fn main() {
// Create a container
let container = Container::new();
// Register a singleton (created immediately, shared everywhere)
container.singleton(Database {
url: "postgres://localhost/mydb".into(),
});
// Register with lazy initialization (created on first access)
let c = container.clone();
container.lazy(move || UserService {
db: c.get().unwrap(),
});
// Resolve services
let db = container.get::<Database>().unwrap();
let users = container.get::<UserService>().unwrap();
println!("Connected to: {}", db.url);
}
Created immediately and shared across all resolutions:
container.singleton(Config { debug: true });
let config1 = container.get::<Config>().unwrap();
let config2 = container.get::<Config>().unwrap();
// config1 and config2 point to the same instance
Created on first access, then shared:
container.lazy(|| ExpensiveService::new());
// Service is created here on first call
let svc = container.get::<ExpensiveService>().unwrap();
New instance created on every resolution:
container.transient(|| RequestId(generate_id()));
let id1 = container.get::<RequestId>().unwrap();
let id2 = container.get::<RequestId>().unwrap();
// id1 and id2 are different instances
Create child containers that inherit from their parent:
// Root container with shared services
let root = Container::new();
root.singleton(AppConfig { name: "MyApp".into() });
// Per-request scope
let request_scope = root.scope();
request_scope.singleton(RequestContext { id: "req-123".into() });
// Child can access parent services
assert!(request_scope.contains::<AppConfig>());
assert!(request_scope.contains::<RequestContext>());
// Parent cannot access child services
assert!(!root.contains::<RequestContext>());
Override parent services in child scopes:
let root = Container::new();
root.singleton(Database { url: "production".into() });
let test_scope = root.scope();
test_scope.singleton(Database { url: "test".into() });
// Root still has production
let root_db = root.get::<Database>().unwrap();
assert_eq!(root_db.url, "production");
// Test scope has override
let test_db = test_scope.get::<Database>().unwrap();
assert_eq!(test_db.url, "test");
Use the #[derive(Inject)] macro for automatic dependency resolution:
use dependency_injector::{Container, Inject};
use std::sync::Arc;
#[derive(Clone)]
struct Database {
url: String,
}
#[derive(Clone)]
struct Cache {
size: usize,
}
// Derive Inject to generate from_container() method
#[derive(Inject)]
struct UserService {
#[inject]
db: Arc<Database>,
#[inject]
cache: Arc<Cache>,
#[inject(optional)]
logger: Option<Arc<Logger>>, // Optional dependency
request_count: u64, // Uses Default::default()
}
fn main() {
let container = Container::new();
container.singleton(Database { url: "postgres://localhost".into() });
container.singleton(Cache { size: 1024 });
// Automatically resolve all #[inject] fields
let service = UserService::from_container(&container).unwrap();
}
| Attribute | Field Type | Description |
|---|---|---|
#[inject] |
Arc<T> |
Required dependency - fails if not registered |
#[inject(optional)] |
Option<Arc<T>> |
Optional dependency - None if not registered |
| (none) | Any type with Default |
Uses Default::default() |
Armature is a Rust HTTP framework with built-in DI support:
use armature::prelude::*;
use dependency_injector::Container;
#[injectable]
#[derive(Clone)]
struct Database { url: String }
#[controller("/api")]
struct UserController {
db: Arc<Database>,
}
#[controller]
impl UserController {
#[get("/users")]
async fn get_users(&self) -> Result<Json<Vec<User>>, Error> {
let users = self.db.query_users().await?;
Ok(Json(users))
}
}
#[module]
struct AppModule {
#[controllers]
controllers: (UserController,),
#[providers]
providers: (Database,),
}
#[tokio::main]
async fn main() -> Result<(), Error> {
Application::create(AppModule)
.listen("0.0.0.0:3000")
.await
}
| Method | Description |
|---|---|
Container::new() |
Create a new container |
singleton(service) |
Register an immediate singleton |
lazy(factory) |
Register a lazy-initialized singleton |
transient(factory) |
Register a transient service |
get::<T>() |
Resolve a service by type |
contains::<T>() |
Check if a service is registered |
remove::<T>() |
Remove a service registration |
scope() |
Create a child container |
Enable structured logging to see what the container is doing:
use dependency_injector::{Container, logging};
fn main() {
// Initialize logging (JSON by default with logging-json, pretty with logging-pretty)
logging::init();
// Or use the builder for more control
logging::builder()
.debug() // Set log level
.pretty() // Use pretty output
.di_only() // Only show dependency-injector logs
.init();
let container = Container::new();
// Logs: DEBUG dependency_injector: Creating new root DI container
container.singleton(MyService { name: "test".into() });
// Logs: DEBUG dependency_injector: Registering singleton service
let _ = container.get::<MyService>();
// Logs: TRACE dependency_injector: Resolving service
}
cargo run --features logging-json
{"timestamp":"2024-01-01T00:00:00.000Z","level":"DEBUG","fields":{"message":"Creating new root DI container","depth":0},"target":"dependency_injector"}
cargo run --features logging-pretty
2024-01-01T00:00:00.000Z DEBUG dependency_injector: Creating new root DI container, depth: 0
The container is built for high-performance scenarios:
DashMapArc sharing| Language | Best Library | Singleton Resolution | Mixed Workload (100 ops) |
|---|---|---|---|
| Rust | dependency-injector | 17-32 ns | 2.2 µs |
| Go | sync.Map | 15 ns | 7 µs |
| C# | MS.Extensions.DI | 208 ns | 31 µs |
| Python | dependency-injector | 95 ns | 15.7 µs |
| Node.js | inversify | 1,829 ns | 15 µs |
Rust is 6-14x faster than other languages' popular DI libraries for mixed workloads.
See BENCHMARK_COMPARISON.md for full cross-language benchmarks
| Library | Singleton Resolution | Mixed Workload |
|---|---|---|
| dependency-injector | ~17-27 ns | 2.2 µs |
| shaku | ~17-21 ns | 2.5-15 µs |
| ferrous-di | ~57-70 ns | 7.6-11 µs |
See RUST_DI_COMPARISON.md for Rust-specific benchmarks
Run benchmarks locally:
# Internal benchmarks
cargo bench --bench container_bench
# Comparison against other Rust DI crates
cargo bench --bench comparison_bench
The library provides C-compatible FFI bindings for use from other languages:
| Language | Bindings | Example |
|---|---|---|
| Go | Native CGO | ffi/go/ |
| Python | ctypes | ffi/python/ |
| Node.js | ffi-napi | ffi/nodejs/ |
| C# | P/Invoke | ffi/csharp/ |
| C/C++ | Header file | ffi/dependency_injector.h |
# Build the shared library (cdylib)
cargo rustc --release --features ffi --crate-type cdylib
# Output locations:
# Linux: target/release/libdependency_injector.so
# macOS: target/release/libdependency_injector.dylib
# Windows: target/release/dependency_injector.dll
from dependency_injector import Container
container = Container()
container.singleton("config", {"database": "postgres://localhost"})
config = container.get("config")
print(config) # {"database": "postgres://localhost"}
package main
import "github.com/pegasusheavy/dependency-injector/ffi/go/di"
func main() {
container := di.NewContainer()
defer container.Free()
container.RegisterSingleton("config", `{"database": "postgres://localhost"}`)
config, _ := container.Resolve("config")
fmt.Println(config)
}
See ffi/README.md for complete FFI documentation
The project includes comprehensive fuzz testing using cargo-fuzz:
# Install cargo-fuzz (requires nightly)
cargo install cargo-fuzz
# List available fuzz targets
cargo +nightly fuzz list
# Run a specific fuzz target
cargo +nightly fuzz run fuzz_container
# Run for a specific duration
cargo +nightly fuzz run fuzz_container -- -max_total_time=60
| Target | Description |
|---|---|
fuzz_container |
Basic registration and resolution operations |
fuzz_scoped |
Hierarchical scopes and parent chain resolution |
fuzz_concurrent |
Multi-threaded concurrent access patterns |
fuzz_lifecycle |
Lazy initialization, transients, and locking |
Contributions are welcome! Please read our Contributing Guide before submitting a PR.
git checkout -b feature/amazing-feature)git commit -m 'feat: add amazing feature')git push origin feature/amazing-feature)Licensed under either of:
at your option.
Inspired by dependency injection patterns from: