| Crates.io | flow-di |
| lib.rs | flow-di |
| version | 0.1.0 |
| created_at | 2025-07-30 16:48:33.076563+00 |
| updated_at | 2025-07-30 16:48:33.076563+00 |
| description | A dependency injection framework for Rust inspired by C# AutoFac and Microsoft.Extensions.DependencyInjection |
| homepage | |
| repository | https://github.com/spartajet/flow-di |
| max_upload_size | |
| id | 1773797 |
| size | 1,418,496 |
A Rust dependency injection framework inspired by C# AutoFac and Microsoft.Extensions.DependencyInjection.
中文文档 | English
Add this to your Cargo.toml:
[dependencies]
flow-di = "0.1.0"
use flow_di::{ContainerBuilder, ServiceProviderExt};
use std::sync::Arc;
// Define service interface
trait IRepository: Send + Sync {
fn get_data(&self) -> String;
}
// Implement service
struct DatabaseRepository {
connection_string: String,
}
impl IRepository for DatabaseRepository {
fn get_data(&self) -> String {
format!("Data from database: {}", self.connection_string)
}
}
fn main() {
// Configure and build container
let provider = ContainerBuilder::new()
.add_instance("localhost:5432".to_string()) // Register configuration
.add_singleton_with_deps::<DatabaseRepository, String>(
|connection_string| DatabaseRepository {
connection_string: (*connection_string).clone(),
}
)
.build();
// Resolve service
let repository = provider.get_required_service::<DatabaseRepository>().unwrap();
println!("{}", repository.get_data());
}
Services registered as singleton will have only one instance throughout the application lifetime:
use flow_di::{ContainerBuilder, ServiceProviderExt};
let provider = ContainerBuilder::new()
.add_singleton_simple::<String, String>(|| "singleton instance".to_string())
.build();
let instance1 = provider.get_required_service::<String>().unwrap();
let instance2 = provider.get_required_service::<String>().unwrap();
// instance1 and instance2 are the same instance
assert!(Arc::ptr_eq(&instance1, &instance2));
Services registered as scoped will share the same instance within the same scope:
use flow_di::{ContainerBuilder, ServiceProviderExt};
let provider = ContainerBuilder::new()
.add_scoped_simple::<String, String>(|| "scoped instance".to_string())
.build();
let mut scope = provider.create_scope().unwrap();
let service1 = scope.get_required_service::<String>().unwrap();
let service2 = scope.get_required_service::<String>().unwrap();
// service1 and service2 are the same instance within the same scope
assert!(Arc::ptr_eq(&service1, &service2));
Services registered as transient will create a new instance for each request:
use flow_di::{ContainerBuilder, ServiceProviderExt};
let provider = ContainerBuilder::new()
.add_transient_simple::<String, String>(|| "transient instance".to_string())
.build();
let instance1 = provider.get_required_service::<String>().unwrap();
let instance2 = provider.get_required_service::<String>().unwrap();
// Each time is a new instance
assert!(!Arc::ptr_eq(&instance1, &instance2));
You can register multiple implementations of the same interface using different names:
use flow_di::{ContainerBuilder, ServiceProviderExt};
// Register named services
let provider = ContainerBuilder::new()
.add_named_singleton_simple::<String, String>("primary", || "primary service".to_string())
.add_named_singleton_simple::<String, String>("secondary", || "secondary service".to_string())
.build();
// Resolve named services
let primary = provider.get_required_keyed_service::<String>("primary").unwrap();
let secondary = provider.get_required_keyed_service::<String>("secondary").unwrap();
assert_eq!(*primary, "primary service");
assert_eq!(*secondary, "secondary service");
Services can depend on other services, and dependencies will be automatically injected:
use flow_di::{ContainerBuilder, ServiceProviderExt};
use std::sync::Arc;
trait ILogger: Send + Sync {
fn log(&self, message: &str);
}
trait IRepository: Send + Sync {
fn get_user(&self, id: i32) -> String;
}
struct ConsoleLogger;
impl ILogger for ConsoleLogger {
fn log(&self, message: &str) {
println!("Log: {}", message);
}
}
struct UserRepository {
logger: Arc<ConsoleLogger>,
}
impl IRepository for UserRepository {
fn get_user(&self, id: i32) -> String {
self.logger.log(&format!("Getting user {}", id));
format!("User {}", id)
}
}
struct UserService {
repository: Arc<UserRepository>,
logger: Arc<ConsoleLogger>,
}
impl UserService {
fn new(repository: Arc<UserRepository>, logger: Arc<ConsoleLogger>) -> Self {
Self { repository, logger }
}
fn get_user(&self, id: i32) -> String {
self.logger.log("UserService::get_user called");
self.repository.get_user(id)
}
}
let provider = ContainerBuilder::new()
// Register logger
.add_singleton_simple::<ConsoleLogger, ConsoleLogger>(|| ConsoleLogger)
// Register repository with logger dependency
.add_scoped_with_deps::<UserRepository, UserRepository, ConsoleLogger>(
|logger| UserRepository { logger }
)
// Register service with multiple dependencies
.add_transient_with_deps2::<UserService, UserService, UserRepository, ConsoleLogger>(
|repository, logger| UserService::new(repository, logger)
)
.build();
let mut scope = provider.create_scope().unwrap();
let user_service = scope.get_required_service::<UserService>().unwrap();
let user = user_service.get_user(123);
println!("{}", user); // Output: User 123
Service scopes provide isolation for scoped services and automatic resource cleanup:
use flow_di::{ContainerBuilder, ServiceProviderExt};
let provider = ContainerBuilder::new()
.add_scoped_simple::<String, String>(|| "scoped service".to_string())
.build();
// Create scope
let mut scope = provider.create_scope().unwrap();
// ServiceScope also implements ServiceProvider, so it can be used to resolve services
let service = scope.get_required_service::<String>().unwrap();
println!("{}", service);
// Release scope
scope.dispose();
// Check if disposed
let is_disposed = scope.is_disposed();
assert!(is_disposed);
The ContainerBuilder provides a fluent API for registering services:
add_singleton<TService, TImplementation>(factory) - Register singleton serviceadd_scoped<TService, TImplementation>(factory) - Register scoped serviceadd_transient<TService, TImplementation>(factory) - Register transient serviceadd_instance<T>(instance) - Register singleton instanceadd_singleton_simple<TService, TImplementation>(factory) - Register singleton service without dependenciesadd_scoped_simple<TService, TImplementation>(factory) - Register scoped service without dependenciesadd_transient_simple<TService, TImplementation>(factory) - Register transient service without dependenciesadd_singleton_with_deps<TService, TImplementation, TDep>(factory) - Register singleton with one dependencyadd_singleton_with_deps2<TService, TImplementation, TDep1, TDep2>(factory) - Register singleton with two dependenciesscoped and transientadd_named_singleton<TService, TImplementation>(name, factory) - Register named singletonadd_named_scoped<TService, TImplementation>(name, factory) - Register named scoped serviceadd_named_transient<TService, TImplementation>(name, factory) - Register named transient serviceadd_named_instance<T>(name, instance) - Register named singleton instanceThe ServiceProvider provides methods for resolving services:
get_service_raw(&self, key: &ServiceKey) - Get raw service as Arc<dyn Any>get_service<T>() - Get optional service of type Tget_required_service<T>() - Get required service of type T (panics if not found)get_keyed_service<T>(key) - Get optional named serviceget_required_keyed_service<T>(key) - Get required named servicecreate_scope() - Create a new service scopeService scopes provide isolated contexts for scoped services:
ServiceProviderExt traitFlow-DI is designed with object safety in mind:
ServiceProvider trait is object-safe and can be used as dyn ServiceProviderServiceProviderExt provides generic methods via extension trait patternServiceProvider implementorsAll components are thread-safe:
Arc<T> for service instances to enable safe sharingSee the examples directory for complete examples:
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.