rust_dix/lib.rs
1#![forbid(unsafe_code)]
2
3//! rust-dix — Rust Dependency Injection Framework
4//!
5//! A dependency injection container for Rust inspired by
6//! Microsoft.Extensions.DependencyInjection (MEDI).
7//!
8//! # Quick start
9//!
10//! ```rust
11//! use rust_dix::*;
12//! use std::sync::Arc;
13//!
14//! // Trait-based interface (recommended)
15//! trait ILogger: Send + Sync {
16//! fn log(&self, msg: &str);
17//! }
18//!
19//! struct ConsoleLogger;
20//! impl ILogger for ConsoleLogger {
21//! fn log(&self, msg: &str) { println!("[LOG] {}", msg); }
22//! }
23//!
24//! // Register as trait interface
25//! let provider = ServiceCollection::new()
26//! .singleton::<dyn ILogger>(|_| Arc::new(ConsoleLogger))
27//! .build()
28//! .unwrap();
29//!
30//! // Resolve as trait
31//! let logger: Arc<dyn ILogger> = provider.get().unwrap();
32//! logger.log("Hello");
33//! ```
34//!
35//! # Keyed services (strategy pattern)
36//!
37//! ```rust
38//! use rust_dix::*;
39//! use std::sync::Arc;
40//!
41//! trait Strategy: Send + Sync { fn execute(&self) -> &'static str; }
42//!
43//! struct FastStrategy;
44//! impl Strategy for FastStrategy { fn execute(&self) -> &'static str { "fast" } }
45//!
46//! struct SafeStrategy;
47//! impl Strategy for SafeStrategy { fn execute(&self) -> &'static str { "safe" } }
48//!
49//! let provider = ServiceCollection::new()
50//! .keyed_singleton::<dyn Strategy>("fast", |_| Arc::new(FastStrategy))
51//! .keyed_singleton::<dyn Strategy>("safe", |_| Arc::new(SafeStrategy))
52//! .build()
53//! .unwrap();
54//!
55//! let fast: Arc<dyn Strategy> = provider.get_keyed("fast").unwrap();
56//! assert_eq!(fast.execute(), "fast");
57//! ```
58//!
59//! # Build-time validation
60//!
61//! rust-dix validates dependencies during `build()` to catch errors early:
62//!
63//! ```rust
64//! # use rust_dix::*;
65//! # use std::sync::Arc;
66//! # struct A;
67//! # struct B;
68//! // Circular dependency detection — factories resolve deps via `get_any`
69//! // (the `get::<T>()` method requires `Self: Sized` and cannot be called
70//! // on `&dyn IServiceResolver`).
71//! let result = ServiceCollection::new()
72//! .singleton(|r| {
73//! let _b = r.get_any(std::any::type_name::<B>()); // A depends on B
74//! Arc::new(A)
75//! })
76//! .singleton(|r| {
77//! let _a = r.get_any(std::any::type_name::<A>()); // B depends on A → CIRCULAR!
78//! Arc::new(B)
79//! })
80//! .build();
81//!
82//! assert!(result.is_err());
83//! if let Err(RdiError::CircularDependency(cycle)) = result {
84//! println!("Circular dependency detected: {}", cycle);
85//! }
86//! ```
87//!
88//! # Async support
89//!
90//! For services that require async initialization (e.g., database
91//! connections, remote config loading), use `build_async()` and
92//! the `async_*` registration methods. `build_async()` returns
93//! `Arc<ServiceProvider>` so that `provider_arc()` is available for
94//! async resolution:
95//!
96//! ```rust,ignore
97//! use rust_dix::*;
98//! use std::sync::Arc;
99//!
100//! struct DbPool { conn: String }
101//!
102//! async fn connect_to_db() -> DbPool {
103//! // async connection setup...
104//! DbPool { conn: "connected".into() }
105//! }
106//!
107//! # async fn run() {
108//! let provider = ServiceCollection::new()
109//! .async_singleton(|_| Box::pin(async {
110//! Arc::new(connect_to_db().await)
111//! }))
112//! .build_async()
113//! .await
114//! .unwrap();
115//!
116//! let pool: Arc<DbPool> = provider.get_async().await.unwrap();
117//! # }
118//! ```
119//!
120//! # Features
121//!
122//! - **Three lifetimes**: Singleton, Scoped, Transient
123//! - **Keyed services**: multiple implementations of the same trait by key
124//! - `keyed_singleton(key, factory)` — shared globally
125//! - `keyed_scoped(key, factory)` — shared within scope
126//! - `keyed_transient(key, factory)` — new instance each time
127//! - **Constructor injection**: `#[derive(Inject)]` on structs
128//! - **Attribute-based auto-registration**: `#[rust_dix::inject]` on structs or trait impls
129//! - **Compile-time module scanning**: `#[rust_dix::module]` collects `register!()` declarations
130//! - **Cross-DLL support**: named service registry for cdylib plugins
131//! - **Layered containers**: child-first resolution via `ServiceProviderWrapper`
132//! - **Build-time validation**: circular dependency detection during `build()`
133//! - **Scope validation**: scoped services cannot be resolved from the root
134//! provider by default (MEDI `ValidateScopes` parity; opt out with
135//! [`ServiceProviderOptions::lenient`])
136//! - **Deterministic lifecycle**: eager singletons are created in registration
137//! order; [`ServiceProvider::dispose`] / [`Scope::dispose`] release cached
138//! instances in reverse creation order
139//! - **MEDI registration semantics**: the last registration wins for a single
140//! service; `get_all` returns unkeyed implementations in registration order
141//! (`get_all_keyed` for keyed ones)
142//!
143//! # Crate layout
144//!
145//! | Module | Description |
146//! |--------|-------------|
147//! | [`ServiceCollection`] | Register services with a builder API |
148//! | [`ServiceProvider`] | The root DI container |
149//! | [`Scope`] / [`ServiceScope`] | Scoped container (one per scope) |
150//! | [`IServiceResolver`] | Resolution trait (implemented by `ServiceProvider` & `Scope`) |
151//! | [`ServiceProviderWrapper`] | Child-first layered container |
152//! | [`ServiceLifetime`] | Enum: Singleton, Scoped, Transient |
153//! | [`IProvider`] | Unified trait for resolution + named registry |
154//! | [`RdiError`] | Error types (includes `CircularDependency`) |
155//!
156//! # Proc-macros
157//!
158//! - `#[derive(Inject)]` — auto-generates constructor injection code
159//! - `#[rust_dix::inject]` — auto-registration on structs or trait impls
160//! - `#[rust_dix::module]` — compile-time module scanning
161//! - `rust_dix::register!(...)` — declare services inside `#[rust_dix::module]`
162
163pub mod bridge;
164mod cache;
165pub mod collection;
166pub mod entry;
167pub mod error;
168pub mod key;
169#[doc(hidden)]
170pub mod macros_support;
171pub mod provider;
172pub mod registration;
173pub mod scope;
174mod validation;
175pub mod wrapper;
176
177// ── Core types (MEDI-inspired naming) ──
178
179/// Service collection — register services, then call `.build()`.
180///
181/// Analogous to `IServiceCollection` in Microsoft.Extensions.DependencyInjection.
182pub use collection::ServiceCollection;
183
184/// Built service provider — the root DI container (read-only after build).
185///
186/// 兼具 MEDI 中 `IServiceProvider` 与 root scope 双重身份:
187/// - 作为 `IServiceProvider`:提供类型/键控/命名解析入口。
188/// - 作为 root scope:**默认启用严格 scope 校验**(对齐 MEDI/ASP.NET Core 开发环境的
189/// `ValidateScopes`),从根解析 Scoped 服务返回 [`RdiError::ScopedFromRootProvider`];
190/// 在 [`ServiceProvider::scope`] 创建的 Scope 内解析则正常缓存复用。若确实需要
191/// 0.7 的"根 scope 内单例化"行为,可用
192/// [`ServiceProviderOptions::lenient`] + [`ServiceCollection::build_with`]。
193/// - 与 MEDI 对齐,容器自身可作为内建服务解析:`provider.get::<ServiceProvider>()`
194/// 与 `#[inject] Arc<ServiceProvider>`(或专用 `#[inject(provider)]`)均可用,
195/// eager singleton 与懒解析服务一致。
196/// - [`ServiceProvider::dispose`] 按创建逆序释放 singleton / root-scoped 实例并
197/// 标记容器不可用(后续解析返回 [`RdiError::ProviderDisposed`])。
198/// - ⚠️ singleton 持有 `Arc<ServiceProvider>` 会形成 `容器 → singleton → 容器` 的
199/// 强引用环,容器不再可能被回收;需要按任务创建 scope 的 singleton 应优先注入
200/// [`ScopeFactory`](内置实现为弱引用,无环)。构造函数内也不要通过注入的
201/// provider 解析服务(会被拒绝),请保存句柄并在方法中延迟解析。
202///
203/// Analogous to `IServiceProvider` in MEDI.
204pub use provider::ServiceProvider;
205
206/// Key used by keyed registrations, including the `AnyKey` fallback sentinel.
207pub use key::{KeyedService, ServiceKey};
208
209/// Handle for propagating the async resolution chain into detached tasks
210/// (enables cross-task dependency cycle detection).
211pub use provider::AsyncFactoryContext;
212
213/// Options for [`ServiceCollection::build_with`] /
214/// [`ServiceCollection::build_async_with`].
215///
216/// Defaults match MEDI/ASP.NET Core development-time scope validation:
217/// `validate_scopes = true`. Use [`ServiceProviderOptions::lenient`] to allow
218/// resolving scoped services from the root provider (pre-0.8 behavior).
219pub use provider::ServiceProviderOptions;
220
221/// Service descriptor containing registration metadata.
222pub use entry::ServiceDescriptor;
223
224/// Service lifetime enum: [`Singleton`](ServiceLifetime::Singleton),
225/// [`Scoped`](ServiceLifetime::Scoped), [`Transient`](ServiceLifetime::Transient).
226pub use entry::ServiceLifetime;
227
228/// Core resolution trait. Both [`ServiceProvider`] and [`Scope`] implement this.
229pub use entry::IServiceResolver;
230
231/// A scoped service provider — created via [`ServiceProvider::scope`].
232///
233/// Analogous to `IServiceScope` in MEDI.
234pub use scope::Scope;
235
236/// Alias for [`Scope`] with MEDI-inspired naming (`IServiceScope`).
237pub use scope::Scope as ServiceScope;
238
239/// Factory trait for creating independent scopes.
240///
241/// The built provider always offers an implementation (no manual registration
242/// required): `provider.get::<dyn ScopeFactory>()` and
243/// `#[inject] Arc<dyn ScopeFactory>` fields both work, including inside
244/// eagerly built singletons (e.g. background workers that need one scope per
245/// tick). Explicit `singleton::<dyn ScopeFactory>(...)` registrations take
246/// precedence.
247///
248/// The built-in implementation holds a `Weak` reference to the container, so a
249/// singleton may store it without creating an `Arc` cycle; the container must
250/// therefore outlive services created from it.
251pub use scope::ScopeFactory;
252
253/// Internal types used by the container.
254pub use entry::{ServiceEntry, ServiceFactory};
255
256/// Wrapper combining child + root provider with child-first resolution.
257pub use wrapper::ServiceProviderWrapper;
258
259// ── Plugin / cross-DLL support ──
260
261/// Unified trait for service resolution and named registry access.
262pub use bridge::IProvider;
263
264// ── Error types ──
265
266/// Error types returned from service resolution.
267pub use error::{inject_arc, RdiError};
268
269// ── Runtime registration ──
270
271pub use registration::ServiceRegistration;
272
273/// Re-export of `inventory` so that `#[rust_dix::inject]` generated code
274/// can call `rust_dix::inventory::submit!` without requiring downstream crates
275/// to add `inventory` to their own `Cargo.toml`.
276#[doc(hidden)]
277pub use inventory;
278
279// ── Proc-macros ──
280
281// `inject` is the attribute macro (placed on structs or trait impls).
282// `register` is the function-like macro used inside `#[rust_dix::module]`.
283pub use rust_dix_macros::{inject, module, register, Inject};