rust-dicore is the core crate of the rust-dicore framework — a Rust dependency injection
container inspired by Microsoft.Extensions.DependencyInjection (MEDI).
[]
= "0.1"
When should you use this?
1. You want to decouple service creation from usage
Instead of MyService::new(dep1, dep2, dep3) scattered across your codebase,
you declare once how services are constructed and let the container wire them
up automatically.
// Without DI: every call site must know how to build everything
let svc = new;
// With DI: declare once, resolve anywhere
let provider = new
.singleton
.singleton
.transient
.build
.unwrap;
let svc: = provider.get;
2. You need to swap implementations for testing
Change one registration line — no need to touch the code that consumes the service.
3. You have objects with different lifetimes
Some services should be shared globally (database pool), some per-request (HTTP context), some created fresh each time (value objects). rust-dicore gives you three lifetimes to model this naturally.
| Lifetime | Behavior | Typical use |
|---|---|---|
| Singleton | Created once, shared everywhere | Database pool, config, event bus |
| Scoped | Created once per scope, shared within scope | HTTP request context, unit of work, transaction |
| Transient | Created every time you resolve it | Value objects, DTOs, lightweight stateless services |
let provider = new
.singleton // one pool for the app
.scoped // one per request
.transient // new each time
.build
.unwrap;
4. You want multiple instances of the same type
Keyed services let you register several implementations of the same trait, each distinguished by a string key.
let provider = new
.
.
.build
.unwrap;
let fast: = provider.get_keyed;
let safe: = provider.get_keyed;
5. You are building a plugin / modular system
- Layered containers: child-first, root-fallback resolution for plugin isolation. The plugin sees its own services first; host services are visible as fallback.
- Named services: register services by string name — critical for cdylib
plugins where Rust's
TypeIddiffers across compilation units. IServiceLocator: pass a unified DI interface to external modules that don't depend on rust-dicore directly.
Quick Reference · API
Registration (ServiceCollection)
| Method | Lifetime | Use when |
|---|---|---|
.from_injected() |
mixed | Collect all #[rust_dicore::inject] annotations (see below) |
.singleton(f) |
Singleton | You need exactly one instance |
.scoped(f) |
Scoped | You need one instance per scope |
.transient(f) |
Transient | You need a new instance every time |
.keyed(k, f) |
Singleton | You need multiple named instances |
.keyed_scoped(k, f) |
Scoped | Multiple named scoped instances |
.keyed_transient(k, f) |
Transient | Multiple named transient instances |
.instance(arc) |
Singleton | You already have a built Arc<T> |
.try_add(f) |
Singleton | Only register if not already present |
.singleton_value(v) |
Singleton | Register a plain value (wraps in Arc) |
.add(lt, f) |
any | Specify lifetime explicitly |
The factory closure f receives &dyn IServiceResolver so you can resolve
dependencies:
new
.singleton
.transient
.build
.unwrap;
#[rust_dicore::inject] — Attribute-based auto-registration (recommended)
The preferred way to register services is to annotate structs directly:
use *;
// registers as concrete type
// registers as trait
// multi-trait
;
// Then build from all annotations in one call:
let provider = from_injected.build.unwrap;
The #[rust_dicore::inject] attribute automatically generates constructor code
(like #[derive(Inject)]) and registers the service via inventory
(on Linux, macOS, and Windows).
Supports both named-field structs and unit structs (zero fields):
// Unit struct — zero fields, works out of the box
;
// Named struct — fields resolved from DI container
Resolution (ServiceProvider / Scope)
| Method | Returns | Behavior |
|---|---|---|
.get::<T>() |
Arc<T> |
Panics if not registered |
.get_service::<T>() |
Option<Arc<T>> |
Returns None if not registered |
.get_required_service::<T>() |
Arc<T> |
Panics with descriptive message |
.get_keyed::<T>(key) |
Arc<T> |
Panics if key not found |
.try_get_keyed::<T>(key) |
Option<Arc<T>> |
Returns None if key not found |
.get_all::<T>() |
Vec<Arc<T>> |
All registered instances (keyed + unkeyed) |
.get_named::<T>(name) |
Arc<T> |
Named resolution (cross-DLL) |
.get_named_any::<T>(name) |
Option<Arc<T>> |
Named, returns None if missing |
.create_scope() |
Scope |
New scope for scoped services |
T can be a concrete type or dyn Trait:
let svc: = provider.get;
let plugin: = provider.get;
let all: = provider.get_all;
Flexible Application Patterns
🔹 Three-layered architecture (Controller → Service → Repository)
let provider = new
.singleton
.transient
.transient
.transient
.build
.unwrap;
🔹 Strategy pattern with keyed services
let provider = new
.
.
.
.build
.unwrap;
🔹 Scoped per-request (web server)
🔹 Plugin isolation with ServiceProviderWrapper
let host_provider = new
.singleton
.build.unwrap;
let plugin_provider = new
.singleton
.build.unwrap;
let wrapper = new;
// PluginService resolved from plugin container
// HostService falls back to host container
🔹 Cross-DLL plugin with named services
// Host process
let provider = new
.singleton
.build.unwrap;
provider.register_named;
// Plugin loaded from cdylib (separate compilation unit)
let bus = host.;
🔹 External system integration via IServiceLocator
let locator: = new;
// Pass to third-party code or FFI boundary
Architecture
┌──────────────────────────────────────────────┐
│ ServiceCollection │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │singleton │ │ scoped │ │ transient │ │
│ │ .keyed() │ │.keyed_*()│ │ .instance() │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
│ │ .build() │
│ ▼ │
│ ServiceProvider │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ .get() │ │.get_keyed│ │ .create_scope│ │
│ │ .get_all │ │.get_named│ │ │ │
│ └──────────┘ └──────────┘ └──────┬───────┘ │
│ │ │
└────────────────────────────────────┼─────────┘
│
▼
┌──────────┐
│ Scope │
│ .get() │
│ scoped │
│ cached │
└──────────┘
Relationship with rust-dicore-macros
rust-dicore works with or without rust-dicore-macros. The macros
provide three compile-time conveniences:
#[rust_dicore::inject(...)](recommended) — Attribute macro that combines constructor generation + auto-registration. One annotation on a struct is all you need —ServiceCollection::from_injected()collects everything.#[derive(Inject)]— Generate the factory function automatically from struct fields. Used internally by#[rust_dicore::inject].#[rust_dicore::module]— Collectrust_dicore::inject!()declarations at compile time and generate a complete provider builder. Useful for external types, conditional compilation, and centralized management.
See rust-dicore-macros/README.md for details.
License
MIT.