flow-di

Crates.ioflow-di
lib.rsflow-di
version0.1.0
created_at2025-07-30 16:48:33.076563+00
updated_at2025-07-30 16:48:33.076563+00
descriptionA dependency injection framework for Rust inspired by C# AutoFac and Microsoft.Extensions.DependencyInjection
homepage
repositoryhttps://github.com/spartajet/flow-di
max_upload_size
id1773797
size1,418,496
(spartajet)

documentation

README

Flow-DI

Rust Crates.io Documentation License: MIT

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 service
  • add_scoped<TService, TImplementation>(factory) - Register scoped service
  • add_transient<TService, TImplementation>(factory) - Register transient service
  • add_instance<T>(instance) - Register singleton instance

Simple Registration Methods (No Dependencies)

  • add_singleton_simple<TService, TImplementation>(factory) - Register singleton service without dependencies
  • add_scoped_simple<TService, TImplementation>(factory) - Register scoped service without dependencies
  • add_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 dependency
  • add_singleton_with_deps2<TService, TImplementation, TDep1, TDep2>(factory) - Register singleton with two dependencies
  • Similar methods for scoped and transient

Named Service Methods

  • add_named_singleton<TService, TImplementation>(name, factory) - Register named singleton
  • add_named_scoped<TService, TImplementation>(name, factory) - Register named scoped service
  • add_named_transient<TService, TImplementation>(name, factory) - Register named transient service
  • add_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 as Arc<dyn Any>

Extension Methods (via ServiceProviderExt trait)

  • get_service<T>() - Get optional service of type T
  • get_required_service<T>() - Get required service of type T (panics if not found)
  • get_keyed_service<T>(key) - Get optional named service
  • get_required_keyed_service<T>(key) - Get required named service
  • create_scope() - Create a new service scope

ServiceScope

Service scopes provide isolated contexts for scoped services:

  • Implements ServiceProviderExt trait
  • Automatic resource cleanup on disposal
  • Thread-safe operations
  • Nested scope support

Architecture

Object Safety

Flow-DI is designed with object safety in mind:

  • ServiceProvider trait is object-safe and can be used as dyn ServiceProvider
  • ServiceProviderExt provides generic methods via extension trait pattern
  • Automatic implementation for all ServiceProvider implementors

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

Commit count: 0

cargo fmt