# SaDi - Semi-automatic Dependency Injector
[](https://crates.io/crates/sadi)
[](https://docs.rs/sadi)
[](LICENSE)
[](https://github.com/JoaoPedro61/sadi/actions/workflows/CI.yml)
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.
## โจ Features
- ๐ **Type-Safe**: Leverages Rust's type system for compile-time safety
- ๐ **Transient Services**: Create new instances on each request
- ๐ **Singleton Services**: Shared instances with reference counting
- ๐ **Circular Detection**: Prevents infinite loops in dependency graphs
- โ **Error Handling**: Comprehensive error types with detailed messages
- ๐ **Optional Logging**: Tracing integration with feature gates
- ๐ **Zero-Cost Abstractions**: Feature gates enable compile-time optimization
## ๐ฆ Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
sadi = "0.2.1"
```
## ๐ Quick Start
```rust
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"));
}
```
## ๐ Usage Guide
### Service Registration
#### Transient Services
Create new instances on each request. The default `bind` registration is transient:
```rust
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();
```
#### Singleton Services
Create once and share across all dependents. Use the `singleton` annotation in `bind`:
```rust
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));
```
### Error Handling
SaDi provides both panicking and non-panicking variants:
```rust
use sadi::{Container, Error};
let c = Container::new();
// 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),
}
```
### Dependency Injection
Services can depend on other services. Use the `container!` macro to register bindings concisely:
```rust
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();
```
## ๐ Advanced Features
### Circular Dependency Detection
SaDi automatically detects and prevents circular dependencies:
```rust
use sadi::Container;
// Example: registering circular dependencies will produce a descriptive error at runtime
let c = Container::new();
Ok(_) => println!("unexpected"),
Err(e) => println!("Circular dependency detected: {}", e),
}
```
### Tracing Integration
Enable the `tracing` feature for automatic logging (the crate's `default` feature includes `tracing`):
```toml
[dependencies]
sadi = { version = "0.2.1", features = ["tracing"] }
```
```rust
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();
}
```
## ๐งช Testing
Run the test suite:
```bash
# 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
```
## ๐ Project Structure
```
sadi/
โโโ sadi/ # library crate
โ โโโ src/ # core implementation (container, macros, types)
โโโ examples/
โ โโโ basic/ # Comprehensive usage example
โโโ README.md # This file
```
## ๐ง Configuration
### Feature Flags
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.
### Environment Variables
When using the tracing feature, you can control logging levels:
```bash
# Set log level
RUST_LOG=debug cargo run --example basic
# Enable only SaDi logs
RUST_LOG=sadi=info cargo run --example basic
```
## ๐ค Contributing
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.
### Development Setup
1. Clone the repository:
```bash
git clone https://github.com/JoaoPedro61/sadi.git
cd sadi
```
2. Run tests:
```bash
cargo test --all-features
```
3. Check formatting:
```bash
cargo fmt --check
```
4. Run clippy:
```bash
cargo clippy -- -D warnings
```
## ๐ Roadmap & TODO
### ๐งต Thread Safety
- [x] **Arc-based Container**: Thread-safe version of SaDi using `Arc` instead of `Rc` (implemented behind the `thread-safe` feature)
- [x] **Send + Sync Services**: Support for `Send + Sync` services in thread-safe mode (enforced by API bounds)
- [x] **Concurrent Access**: Concurrent reads/writes supported via `RwLock`/`Mutex` in thread-safe mode
- [ ] **Lock-free Operations**: Minimize contention in high-concurrency scenarios
### ๐ง Advanced Features
- [x] **Lazy Initialization**: Singleton instances are created on first `provide` (implemented in `Factory`)
- [ ] **Service Metrics**: Internal container metrics for observability (resolution counts, timing)
### ๐ฆ Ecosystem Integration
- [ ] **Async Factory Support**: Enable async/await in factory functions for Tokio/async-std runtimes
- [ ] **Actix-web Integration**: Extension trait and extractors for Actix-web framework
- [ ] **Axum Integration**: Layer and extractor support for Axum web framework
- [ ] **Rocket Integration**: Layer and extractor support for Rocket web framework
### ๐ ๏ธ Developer Experience
- [ ] **Derive Macros**: Auto-generate factory functions from service structs (`#[injectable]`)
- [ ] **Error Suggestions**: Better error messages with fix suggestions
### ๐ Observability
- [ ] **OpenTelemetry**: Built-in telemetry and distributed tracing
- [ ] **Prometheus Metrics**: Expose container metrics for monitoring
### ๐ฏ Performance
- [ ] **Memory Optimization**: Reduced memory footprint for large containers
## ๐ License
This project is licensed under the MIT License - see the [LICENSE](https://github.com/JoaoPedro61/sadi/blob/main/LICENSE) file for details.
## ๐ Acknowledgments
- Inspired by dependency injection patterns from other languages and frameworks
- Built with โค๏ธ using Rust's powerful type system
- Thanks to the Rust community for excellent crates and documentation
---
**Made with โค๏ธ by [Joรฃo Pedro Martins](https://github.com/JoaoPedro61)**