| Crates.io | sadi |
| lib.rs | sadi |
| version | 0.2.2 |
| created_at | 2025-10-09 18:39:51.920676+00 |
| updated_at | 2026-01-13 19:03:04.364013+00 |
| description | Semi-Automatic Dependency Injector |
| homepage | https://github.com/JoaoPedro61/sadi |
| repository | https://github.com/JoaoPedro61/sadi |
| max_upload_size | |
| id | 1876056 |
| size | 54,754 |
A lightweight, type-safe dependency injection container for Rust applications. SaDi provides ergonomic service registration (including trait-object bindings), transient and singleton lifetimes, semi-automatic dependency resolution, and circular dependency detection.
Add this to your Cargo.toml:
[dependencies]
sadi = "0.2.1"
use sadi::{container, bind, Container, Shared};
use std::rc::Rc;
// Define your services (non-thread-safe default uses `Rc` via `Shared`)
struct DatabaseService {
connection_string: String,
}
impl DatabaseService {
fn new() -> Self {
Self {
connection_string: "postgresql://localhost:5432/myapp".to_string(),
}
}
fn query(&self, sql: &str) -> String {
format!("Executing '{}' on {}", sql, self.connection_string)
}
}
struct UserService {
db: Shared<DatabaseService>,
}
impl UserService {
fn new(db: Shared<DatabaseService>) -> Self {
Self { db }
}
fn create_user(&self, name: &str) -> String {
self.db.query(&format!("INSERT INTO users (name) VALUES ('{}')", name))
}
}
fn main() {
// Use the `container!` macro to register bindings ergonomically
let container = container! {
bind(singleton DatabaseService => |_| DatabaseService::new())
bind(UserService => |c| UserService::new(c.resolve::<DatabaseService>().unwrap()))
};
// Resolve and use services
let user_service = container.resolve::<UserService>().unwrap();
println!("{}", user_service.create_user("Alice"));
}
Create new instances on each request. The default bind registration is transient:
use sadi::{container, bind};
use uuid::Uuid;
struct LoggerService {
session_id: String,
}
let c = container! {
bind(LoggerService => |_| LoggerService { session_id: Uuid::new_v4().to_string() })
};
let logger1 = c.resolve::<LoggerService>().unwrap();
let logger2 = c.resolve::<LoggerService>().unwrap();
Create once and share across all dependents. Use the singleton annotation in bind:
use sadi::{container, bind, Shared};
struct ConfigService {
app_name: String,
debug: bool,
}
let c = container! {
bind(singleton ConfigService => |_| ConfigService { app_name: "MyApp".to_string(), debug: true })
};
let config1 = c.resolve::<ConfigService>().unwrap();
let config2 = c.resolve::<ConfigService>().unwrap();
assert!(Shared::ptr_eq(&config1, &config2));
SaDi provides both panicking and non-panicking variants:
use sadi::{Container, Error};
let c = Container::new();
c.bind_concrete::<String, String, _>(|_| "Hello".to_string()).unwrap();
// Resolve (panicking)
let service = c.resolve::<String>().unwrap();
// Non-panicking
match c.resolve::<String>() {
Ok(s) => println!("Got: {}", s),
Err(e) => println!("Error: {}", e),
}
// Trying to resolve an unregistered type
match c.resolve::<u32>() {
Ok(_) => unreachable!(),
Err(e) => println!("Expected error: {}", e),
}
Services can depend on other services. Use the container! macro to register bindings concisely:
use sadi::{container, bind, Shared};
struct DatabaseService { /* ... */ }
impl DatabaseService { fn new() -> Self { DatabaseService {} } }
struct CacheService { /* ... */ }
impl CacheService { fn new() -> Self { CacheService {} } }
struct UserRepository {
db: Shared<DatabaseService>,
cache: Shared<CacheService>,
}
impl UserRepository {
fn new(db: Shared<DatabaseService>, cache: Shared<CacheService>) -> Self {
Self { db, cache }
}
}
let c = container! {
bind(singleton DatabaseService => |_| DatabaseService::new())
bind(singleton CacheService => |_| CacheService::new())
bind(UserRepository => |c| UserRepository::new(c.resolve::<DatabaseService>().unwrap(), c.resolve::<CacheService>().unwrap()))
};
let repo = c.resolve::<UserRepository>().unwrap();
SaDi automatically detects and prevents circular dependencies:
use sadi::Container;
// Example: registering circular dependencies will produce a descriptive error at runtime
let c = Container::new();
// c.bind_concrete::<ServiceA, ServiceA, _>(|c| { let _ = c.resolve::<ServiceB>(); ServiceA });
// c.bind_concrete::<ServiceB, ServiceB, _>(|c| { let _ = c.resolve::<ServiceA>(); ServiceB });
match c.resolve::<ServiceA>() {
Ok(_) => println!("unexpected"),
Err(e) => println!("Circular dependency detected: {}", e),
}
Enable the tracing feature for automatic logging (the crate's default feature includes tracing):
[dependencies]
sadi = { version = "0.2.1", features = ["tracing"] }
use sadi::{container, bind};
use tracing::info;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let c = container! {
bind(singleton DatabaseService => |_| DatabaseService::new())
};
// resolving singletons or other services will be trace-logged when tracing feature is enabled
let _db = c.resolve::<DatabaseService>().unwrap();
}
Run the test suite:
# Run all tests for the workspace
cargo test
# Run tests for the sadi crate only
cargo test -p sadi
# Run with tracing feature
cargo test --features tracing
# Run documentation tests
cargo test --doc -p sadi
# Run example
cargo run --example basic
sadi/
βββ sadi/ # library crate
β βββ src/ # core implementation (container, macros, types)
βββ examples/
β βββ basic/ # Comprehensive usage example
βββ README.md # This file
SaDi exposes a small set of feature flags. See sadi/Cargo.toml for the authoritative list, but the crate currently defines:
thread-safe (enabled by default) β switches internal shared pointer and synchronization primitives to Arc + RwLock/Mutex for thread-safe containers.tracing (enabled by default) β integrates with the tracing crate to emit logs during registration/resolution.The workspace default enables both thread-safe and tracing. To opt out of thread-safe behavior (use Rc instead of Arc), disable the thread-safe feature.
When using the tracing feature, you can control logging levels:
# Set log level
RUST_LOG=debug cargo run --example basic
# Enable only SaDi logs
RUST_LOG=sadi=info cargo run --example basic
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
git clone https://github.com/JoaoPedro61/sadi.git
cd sadi
cargo test --all-features
cargo fmt --check
cargo clippy -- -D warnings
Arc instead of Rc (implemented behind the thread-safe feature)Send + Sync services in thread-safe mode (enforced by API bounds)RwLock/Mutex in thread-safe modeprovide (implemented in Factory)#[injectable])This project is licensed under the MIT License - see the LICENSE file for details.
Made with β€οΈ by JoΓ£o Pedro Martins