1 unstable release
| 0.1.0 | Jul 30, 2025 |
|---|
#429 in Windows APIs
315KB
1.5K
SLoC
Flow-DI
A Rust dependency injection framework inspired by C# AutoFac and Microsoft.Extensions.DependencyInjection.
中文文档 | English
Features
- Three Lifetime Management Types: Singleton, Scoped, Transient
- Keyed Service Support: Named services and key-based service resolution
- Automatic Dependency Injection: Automatic dependency injection when creating service instances
- Fluent API: Easy-to-use service registration API using builder pattern
- Circular Dependency Detection: Automatic detection and prevention of circular dependencies
- Thread Safety: Full support for multi-threaded environments
- Scope Management: Nested scopes and automatic resource cleanup
- Object Safety: Core traits are dyn-compatible for flexible usage patterns
Quick Start
Add this to your Cargo.toml:
[dependencies]
flow-di = "0.1.0"
Basic Usage
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());
}
Lifetime Management
Singleton
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));
Scoped
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));
Transient
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));
Named Services
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");
Dependency Injection
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
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);
API Reference
ContainerBuilder
The ContainerBuilder provides a fluent API for registering services:
Basic Registration Methods
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 instance
Simple Registration Methods (No Dependencies)
add_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 dependencies
Dependency Injection Methods
add_singleton_with_deps<TService, TImplementation, TDep>(factory)- Register singleton with one dependencyadd_singleton_with_deps2<TService, TImplementation, TDep1, TDep2>(factory)- Register singleton with two dependencies- Similar methods for
scopedandtransient
Named Service Methods
add_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 instance
ServiceProvider
The ServiceProvider provides methods for resolving services:
Core Methods (Object-Safe)
get_service_raw(&self, key: &ServiceKey)- Get raw service asArc<dyn Any>
Extension Methods (via ServiceProviderExt trait)
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 scope
ServiceScope
Service scopes provide isolated contexts for scoped services:
- Implements
ServiceProviderExttrait - Automatic resource cleanup on disposal
- Thread-safe operations
- Nested scope support
Architecture
Object Safety
Flow-DI is designed with object safety in mind:
ServiceProvidertrait is object-safe and can be used asdyn ServiceProviderServiceProviderExtprovides generic methods via extension trait pattern- Automatic implementation for all
ServiceProviderimplementors
Thread Safety
All components are thread-safe:
- Services can be resolved from multiple threads
- Scopes can be created and managed concurrently
- Singleton instances are safely shared across threads
Memory Management
- Uses
Arc<T>for service instances to enable safe sharing - Automatic cleanup of scoped services when scopes are disposed
- No memory leaks from circular dependencies (detection prevents them)
Examples
See the examples directory for complete examples:
- Basic Usage - Comprehensive example showing all features
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Inspired by AutoFac for .NET
- Inspired by Microsoft.Extensions.DependencyInjection
Dependencies
~1.5–2.2MB
~41K SLoC