1 unstable release

Uses new Rust 2024

new 0.1.0 Apr 23, 2025

#1028 in Algorithms


Used in 2 crates

MIT license

13KB
164 lines

Illuminate Container

A lightweight dependency injection container for Rust, designed to manage services, factories, and singletons with type-safe bindings. This container supports binding factories, registering singletons, and resolving instances, making it suitable for building modular and testable applications.

Features

  • Type-Safe Bindings: Bind factories to create instances of specific types.
  • Singleton Support: Register singletons to ensure a single instance is reused.
  • Transient Factories: Create new instances on each resolution.
  • Service Providers: Register and boot service providers for modular configuration.
  • Dynamic Resolution: Resolve instances by type using TypeId.

Usage

The Container struct is the core of the library. Below are examples demonstrating how to use its key functionalities.

Setup

Create a new Container instance:

use container::Container;

let mut container = Container::new();

Binding a Factory

Bind a factory to create new instances of a type (transient instances):

#[derive(Debug, PartialEq)]
struct TestService {
    value: i32,
}

impl TestService {
    fn new(value: i32) -> Self {
        Self { value }
    }
}

container.bind(TestService::new(0), |_: &dyn Contract| TestService::new(42));

Resolve a transient instance:

let instance = container.transient(TypeId::of::<TestService>()).unwrap();
let service = instance.downcast_ref::<TestService>().unwrap();
assert_eq!(service.value, 42);

Registering a Singleton

Register a singleton instance to ensure only one instance is created and reused:

let type_id = TypeId::of::<TestService>();
container.singleton(type_id, Box::new(TestService::new(42)));

let instance = container.resolve_any(type_id).unwrap();
let service = instance.downcast_ref::<TestService>().unwrap();
assert_eq!(service.value, 42);

// Subsequent resolutions return the same instance
let instance2 = container.resolve_any(type_id).unwrap();
assert_eq!(
    std::ptr::eq(
        instance.downcast_ref::<TestService>().unwrap(),
        instance2.downcast_ref::<TestService>().unwrap()
    ),
    true
);

Using a Singleton Factory

Register a singleton via a factory, which is executed once to create the instance:

let type_id = TypeId::of::<TestService>();
let factory = |_: &dyn Contract| -> Box<dyn Any> { Box::new(TestService::new(42)) };
container.singleton_factory(type_id, Box::new(factory));

let instance = container.resolve_any(type_id).unwrap();
let service = instance.downcast_ref::<TestService>().unwrap();
assert_eq!(service.value, 42);

// Same instance is reused
let instance2 = container.resolve_any(type_id).unwrap();
assert_eq!(
    std::ptr::eq(
        instance.downcast_ref::<TestService>().unwrap(),
        instance2.downcast_ref::<TestService>().unwrap()
    ),
    true
);

Registering Service Providers

Service providers allow modular configuration of the container:

use illuminate_contracts::support::ServiceProvider;

struct MyProvider;

impl ServiceProvider for MyProvider {
    fn register(&mut self, container: &mut dyn Contract) {
        container.bind_any(TypeId::of::<TestService>(), Box::new(TestService::new(100)));
    }

    fn boot(&mut self, _container: &mut dyn Contract) {
        // Perform boot-time initialization
    }
}

container.register_provider(Box::new(MyProvider));
container.boot();

Notes

  • Singletons are stored separately from transient services to ensure proper instance management.
  • The container uses TypeId for type-safe resolution, so ensure types are unique and correctly registered.
  • Factories are executed each time for transient resolutions but only once for singleton factories.

Testing

The library includes a comprehensive test suite to verify the behavior of bindings, singletons, and service providers. To run the tests:

cargo test

License

This project is licensed under the MIT License.

Dependencies

~4KB