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: typed keys and `AnyKey` fallbacks
36//!
37//! Keys can be any `Any + Send + Sync + Eq + Hash + Debug` value
38//! ([`ServiceKey::new`]); string keys (`"fast"`) remain supported and are stored
39//! as `String` keys. Key equality includes the value type, so
40//! `ServiceKey::new(1u32)` is **not** equal to `"1"`; use
41//! `ServiceKey::from("name")` / `"name".into()` for string-keyed registrations.
42//!
43//! The injection macros accept any **expression** convertible to
44//! [`ServiceKey`] — string literals keep working:
45//! `#[inject(key = "fast")]`, `#[inject(key = ServiceKey::new(Kind::Fast))]`, or a
46//! downstream key value implementing `From<LocalKey> for ServiceKey` (the
47//! official extension path for frameworks; do not duplicate the injection
48//! macros). See the repository reference chapter for details:
49//! <https://github.com/Rust-Framework/rust-dix/blob/main/docs/rust-dix/09-reference/extension-points.md>.
50//!
51//! A registration with [`KeyedService::any_key`] (or
52//! [`ServiceKey::any_key`]) acts as a fallback for keys that have no exact
53//! registration. Singleton/scoped fallback instances are cached per
54//! **(registration, requested key)**; `get_all_keyed` never enumerates them.
55//!
56//! ```rust
57//! use rust_dix::*;
58//! use std::sync::Arc;
59//!
60//! #[derive(Debug, PartialEq, Eq, Hash)]
61//! enum Kind { Fast, Safe }
62//!
63//! trait Strategy: Send + Sync { fn name(&self) -> &'static str; }
64//! struct Fast;
65//! impl Strategy for Fast { fn name(&self) -> &'static str { "fast" } }
66//! struct Fallback;
67//! impl Strategy for Fallback { fn name(&self) -> &'static str { "fallback" } }
68//!
69//! let provider = ServiceCollection::new()
70//! .keyed_singleton::<dyn Strategy>(ServiceKey::new(Kind::Fast), |_| Arc::new(Fast))
71//! .keyed_any_singleton::<dyn Strategy>(|_, _| Arc::new(Fallback))
72//! .build()
73//! .unwrap();
74//!
75//! let fast = provider.get_keyed::<dyn Strategy>(ServiceKey::new(Kind::Fast)).unwrap();
76//! assert_eq!(fast.name(), "fast");
77//! // No exact registration for "unknown": the AnyKey fallback is used.
78//! let fallback = provider.get_keyed::<dyn Strategy>("unknown").unwrap();
79//! assert_eq!(fallback.name(), "fallback");
80//! ```
81//!
82//! For typed-key constructor injection:
83//!
84//! ```rust
85//! use rust_dix::*;
86//! use std::sync::Arc;
87//!
88//! #[derive(Debug, PartialEq, Eq, Hash)]
89//! enum Channel { Primary }
90//! struct Sink;
91//!
92//! #[derive(Inject)]
93//! struct Consumer {
94//! #[inject(key = ServiceKey::new(Channel::Primary))]
95//! sink: Arc<Sink>,
96//! }
97//!
98//! let provider = ServiceCollection::new()
99//! .keyed_singleton(ServiceKey::new(Channel::Primary), |_| Arc::new(Sink))
100//! .transient(|r| __rdi_construct_Consumer(r))
101//! .build()
102//! .unwrap();
103//! let consumer: Arc<Consumer> = provider.get().unwrap();
104//! let _ = &consumer.sink;
105//! ```
106//!
107//! # Build-time validation
108//!
109//! rust-dix validates dependencies during `build()` to catch errors early:
110//!
111//! ```rust
112//! # use rust_dix::*;
113//! # use std::sync::Arc;
114//! # struct A;
115//! # struct B;
116//! // Circular dependency detection — factories resolve deps via `get_any`
117//! // (the `get::<T>()` method requires `Self: Sized` and cannot be called
118//! // on `&dyn IServiceResolver`).
119//! let result = ServiceCollection::new()
120//! .singleton(|r| {
121//! let _b = r.get_any(std::any::type_name::<B>()); // A depends on B
122//! Arc::new(A)
123//! })
124//! .singleton(|r| {
125//! let _a = r.get_any(std::any::type_name::<A>()); // B depends on A → CIRCULAR!
126//! Arc::new(B)
127//! })
128//! .build();
129//!
130//! assert!(result.is_err());
131//! if let Err(RdiError::CircularDependency(cycle)) = result {
132//! println!("Circular dependency detected: {}", cycle);
133//! }
134//! ```
135//!
136//! # Async services
137//!
138//! For services that require async initialization (e.g., database
139//! connections, remote config loading), use `build_async()` and the `async_*`
140//! registration methods. Async singletons are initialized **dependency-first**
141//! (registration order does not matter), so a factory may `await` another async
142//! singleton registered later. `build_async()` returns `Arc<ServiceProvider>`.
143//!
144//! Async resolution is **chain-based**: each `get_async()` future captures the
145//! current resolution chain when constructed, so self/cross dependency cycles
146//! are reported as [`RdiError::CircularDependency`] instead of dead-locking.
147//! To detect cycles across detached tasks, propagate
148//! [`AsyncFactoryContext`] (see [`ServiceProvider::async_context`]) into the
149//! spawned task; without it, a fully detached task cannot inherit the chain.
150//!
151//! ```rust
152//! use rust_dix::*;
153//! use std::sync::Arc;
154//!
155//! struct DbPool { url: String }
156//!
157//! async fn connect() -> DbPool { DbPool { url: "sqlite::memory:".into() } }
158//!
159//! let provider = futures::executor::block_on(
160//! ServiceCollection::new()
161//! .async_singleton(|_| Box::pin(async { Arc::new(connect().await) }))
162//! .build_async(),
163//! )
164//! .unwrap();
165//!
166//! let pool: Arc<DbPool> = futures::executor::block_on(provider.get_async()).unwrap();
167//! assert_eq!(pool.url, "sqlite::memory:");
168//! ```
169//!
170//! # Features
171//!
172//! - **Three lifetimes**: Singleton, Scoped, Transient
173//! - **Keyed services**: multiple implementations by key
174//! - string keys (`"fast"`) and typed keys ([`ServiceKey::new`], e.g. enums)
175//! - [`KeyedService::any_key`] / [`ServiceKey::any_key`] fallback registrations
176//! (`keyed_any_singleton` / `keyed_any_scoped` / `keyed_any_transient`)
177//! - per-`(registration, requested key)` caching for AnyKey fallbacks; exact
178//! registrations take precedence; `get_all_keyed` excludes fallbacks
179//! - **Constructor injection**: `#[derive(Inject)]` on structs
180//! - **Attribute-based auto-registration**: `#[rust_dix::inject]` on structs or
181//! trait impls (`#[inject(key = <expr>)]`, `#[inject(owned)]`,
182//! `#[inject(owned, key = <expr>)]`; key expressions accept string literals,
183//! typed [`ServiceKey`] values, or downstream key types via
184//! `From<LocalKey> for ServiceKey`)
185//! - **Compile-time module scanning**: `#[rust_dix::module]` collects
186//! `register!()` declarations
187//! - **Cross-DLL support**: named service registry for cdylib plugins
188//! - **Layered containers**: child-first resolution via
189//! [`ServiceProviderWrapper`]
190//! - **Build-time validation**: sync/async dependency checks (cycles, captive
191//! dependencies, sync singleton depending on async services) during build
192//! - **Async resolution with cycle detection**: [`ServiceProvider::get_async`] /
193//! [`ServiceProvider::get_keyed_async`]; propagate [`AsyncFactoryContext`] into
194//! detached tasks so cross-task cycles also return
195//! [`RdiError::CircularDependency`]
196//! - **Scope validation**: scoped services cannot be resolved from the root
197//! provider by default (MEDI `ValidateScopes` parity; opt out with
198//! [`ServiceProviderOptions::lenient`]); `get_owned` /
199//! `get_keyed_owned` are the documented exception
200//! - **Deterministic lifecycle**: eager singletons are created in registration
201//! order (async singletons dependency-first); [`ServiceProvider::dispose`] /
202//! [`Scope::dispose`] release cached instances in reverse creation order
203//! - **MEDI registration semantics**: the last registration wins for a single
204//! service; `get_all` returns unkeyed implementations in registration order
205//! (`get_all_keyed` for keyed ones)
206//! - **Typed diagnostics**: [`RdiError`] exposes dedicated variants such as
207//! `SyncSingletonDependsOnAsyncService` and `AsyncServiceRequiresGetAsync`,
208//! plus [`RdiError::is_circular`] (heuristic for factory-panic-wrapped cycles)
209//! - **Engineering baseline**: MSRV **1.95**, `#![forbid(unsafe_code)]`,
210//! fmt/clippy/doc `-D warnings` + tests + RustSec `cargo audit` in CI
211//!
212//! # Diagnostics
213//!
214//! Prefer matching the typed [`RdiError`] variants over strings:
215//!
216//! ```rust
217//! use rust_dix::*;
218//! use std::sync::Arc;
219//!
220//! struct A;
221//! struct B;
222//! let result = ServiceCollection::new()
223//! .singleton(|r| {
224//! let _ = r.get_any(std::any::type_name::<B>());
225//! Arc::new(A)
226//! })
227//! .singleton(|r| {
228//! let _ = r.get_any(std::any::type_name::<A>());
229//! Arc::new(B)
230//! })
231//! .build();
232//!
233//! let err = result.err().expect("cycle must be rejected");
234//! assert!(err.is_circular());
235//! if let RdiError::CircularDependency(cycle) = err {
236//! assert!(cycle.contains("->"));
237//! }
238//! ```
239//!
240//! Sync resolution of an async service returns
241//! `RdiError::AsyncServiceRequiresGetAsync` (use `get_async` / `get_keyed_async`);
242//! registering `async_*` services and calling [`ServiceCollection::build`]
243//! returns `RdiError::AsyncServiceRequiresBuildAsync`.
244//!
245//! # Crate layout
246//!
247//! | Module | Description |
248//! |--------|-------------|
249//! | [`ServiceCollection`] | Register services with a builder API |
250//! | [`ServiceProvider`] | The root DI container |
251//! | [`Scope`] / [`ServiceScope`] | Scoped container (one per scope) |
252//! | [`IServiceResolver`] | Resolution trait (implemented by `ServiceProvider` & `Scope`) |
253//! | [`ServiceProviderWrapper`] | Child-first layered container |
254//! | [`ServiceLifetime`] | Enum: Singleton, Scoped, Transient |
255//! | [`ServiceKey`] / [`KeyedService`] | Typed keys and the `AnyKey` sentinel |
256//! | [`AsyncFactoryContext`] | Propagate the async resolution chain into tasks |
257//! | [`ServiceProviderOptions`] | Scope-validation options (`lenient()`) |
258//! | [`IProvider`] | Unified trait for resolution + named registry |
259//! | [`RdiError`] | Typed errors (cycles, disposed, async diagnostics, ...) |
260//!
261//! # MSRV and security
262//!
263//! - MSRV: **Rust 1.95** (`rust-version` in `Cargo.toml`, verified in CI).
264//! - `#![forbid(unsafe_code)]` in both `rust-dix` and `rust-dix-macros`.
265//! - No network/filesystem access; containers only manage in-memory services.
266//! - Release/security policy: <https://github.com/Rust-Framework/rust-dix/blob/main/SECURITY.md>
267//!
268//! # Proc-macros
269//!
270//! - `#[derive(Inject)]` — auto-generates constructor injection code
271//! - `#[rust_dix::inject]` — auto-registration on structs or trait impls
272//! - `#[rust_dix::module]` — compile-time module scanning
273//! - `rust_dix::register!(...)` — declare services inside `#[rust_dix::module]`
274
275pub mod bridge;
276mod cache;
277pub mod collection;
278pub mod entry;
279pub mod error;
280pub mod key;
281#[doc(hidden)]
282pub mod macros_support;
283pub mod provider;
284pub mod registration;
285pub mod scope;
286mod validation;
287pub mod wrapper;
288
289// ── Core types (MEDI-inspired naming) ──
290
291/// Service collection — register services, then call `.build()`.
292///
293/// Analogous to `IServiceCollection` in Microsoft.Extensions.DependencyInjection.
294pub use collection::ServiceCollection;
295
296/// Built service provider — the root DI container (read-only after build).
297///
298/// 兼具 MEDI 中 `IServiceProvider` 与 root scope 双重身份:
299/// - 作为 `IServiceProvider`:提供类型/键控/命名解析入口。
300/// - 作为 root scope:**默认启用严格 scope 校验**(对齐 MEDI/ASP.NET Core 开发环境的
301/// `ValidateScopes`),从根解析 Scoped 服务返回 [`RdiError::ScopedFromRootProvider`];
302/// 在 [`ServiceProvider::scope`] 创建的 Scope 内解析则正常缓存复用。若确实需要
303/// 0.7 的"根 scope 内单例化"行为,可用
304/// [`ServiceProviderOptions::lenient`] + [`ServiceCollection::build_with`]。
305/// - 与 MEDI 对齐,容器自身可作为内建服务解析:`provider.get::<ServiceProvider>()`
306/// 与 `#[inject] Arc<ServiceProvider>`(或专用 `#[inject(provider)]`)均可用,
307/// eager singleton 与懒解析服务一致。
308/// - [`ServiceProvider::dispose`] 按创建逆序释放 singleton / root-scoped 实例并
309/// 标记容器不可用(后续解析返回 [`RdiError::ProviderDisposed`])。
310/// - ⚠️ singleton 持有 `Arc<ServiceProvider>` 会形成 `容器 → singleton → 容器` 的
311/// 强引用环,容器不再可能被回收;需要按任务创建 scope 的 singleton 应优先注入
312/// [`ScopeFactory`](内置实现为弱引用,无环)。构造函数内也不要通过注入的
313/// provider 解析服务(会被拒绝),请保存句柄并在方法中延迟解析。
314///
315/// Analogous to `IServiceProvider` in MEDI.
316pub use provider::ServiceProvider;
317
318/// Key used by keyed registrations, including the `AnyKey` fallback sentinel.
319pub use key::{KeyedService, ServiceKey};
320
321/// Handle for propagating the async resolution chain into detached tasks
322/// (enables cross-task dependency cycle detection).
323pub use provider::AsyncFactoryContext;
324
325/// Options for [`ServiceCollection::build_with`] /
326/// [`ServiceCollection::build_async_with`].
327///
328/// Defaults match MEDI/ASP.NET Core development-time scope validation:
329/// `validate_scopes = true`. Use [`ServiceProviderOptions::lenient`] to allow
330/// resolving scoped services from the root provider (pre-0.8 behavior).
331pub use provider::ServiceProviderOptions;
332
333/// Service descriptor containing registration metadata.
334pub use entry::ServiceDescriptor;
335
336/// Service lifetime enum: [`Singleton`](ServiceLifetime::Singleton),
337/// [`Scoped`](ServiceLifetime::Scoped), [`Transient`](ServiceLifetime::Transient).
338pub use entry::ServiceLifetime;
339
340/// Core resolution trait. Both [`ServiceProvider`] and [`Scope`] implement this.
341pub use entry::IServiceResolver;
342
343/// A scoped service provider — created via [`ServiceProvider::scope`].
344///
345/// Analogous to `IServiceScope` in MEDI.
346pub use scope::Scope;
347
348/// Alias for [`Scope`] with MEDI-inspired naming (`IServiceScope`).
349pub use scope::Scope as ServiceScope;
350
351/// Factory trait for creating independent scopes.
352///
353/// The built provider always offers an implementation (no manual registration
354/// required): `provider.get::<dyn ScopeFactory>()` and
355/// `#[inject] Arc<dyn ScopeFactory>` fields both work, including inside
356/// eagerly built singletons (e.g. background workers that need one scope per
357/// tick). Explicit `singleton::<dyn ScopeFactory>(...)` registrations take
358/// precedence.
359///
360/// The built-in implementation holds a `Weak` reference to the container, so a
361/// singleton may store it without creating an `Arc` cycle; the container must
362/// therefore outlive services created from it.
363pub use scope::ScopeFactory;
364
365/// Internal types used by the container.
366pub use entry::{ServiceEntry, ServiceFactory};
367
368/// Wrapper combining child + root provider with child-first resolution.
369pub use wrapper::ServiceProviderWrapper;
370
371// ── Plugin / cross-DLL support ──
372
373/// Unified trait for service resolution and named registry access.
374pub use bridge::IProvider;
375
376// ── Error types ──
377
378/// Error types returned from service resolution.
379pub use error::{inject_arc, RdiError};
380
381// ── Runtime registration ──
382
383pub use registration::ServiceRegistration;
384
385/// Re-export of `inventory` so that `#[rust_dix::inject]` generated code
386/// can call `rust_dix::inventory::submit!` without requiring downstream crates
387/// to add `inventory` to their own `Cargo.toml`.
388#[doc(hidden)]
389pub use inventory;
390
391// ── Proc-macros ──
392
393// `inject` is the attribute macro (placed on structs or trait impls).
394// `register` is the function-like macro used inside `#[rust_dix::module]`.
395pub use rust_dix_macros::{inject, module, register, Inject};