dependency_injector/container.rs
1//! High-performance dependency injection container
2//!
3//! The `Container` is the core of the DI system. It stores services and
4//! resolves dependencies with minimal overhead.
5
6use crate::factory::AnyFactory;
7use crate::storage::{ServiceStorage, downcast_arc_unchecked};
8use crate::{DiError, Injectable, Result};
9use std::any::{Any, TypeId};
10use std::cell::UnsafeCell;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, Ordering};
13
14#[cfg(feature = "logging")]
15use tracing::{debug, trace};
16
17// =============================================================================
18// Thread-Local Hot Cache (Phase 5 optimization)
19// =============================================================================
20
21/// Number of slots in the thread-local hot cache (power of 2 for fast indexing)
22/// 4 slots fits in a single cache line and provides good hit rates for typical apps.
23const HOT_CACHE_SLOTS: usize = 4;
24
25/// A cached service entry
26///
27/// Phase 13 optimization: Stores pre-computed u64 hash instead of TypeId
28/// to avoid transmute on every comparison.
29struct CacheEntry {
30 /// Pre-computed hash of TypeId (avoids transmute on lookup)
31 type_hash: u64,
32 /// Pointer to the storage this was resolved from (for scope identity)
33 storage_ptr: usize,
34 /// The cached service
35 service: Arc<dyn Any + Send + Sync>,
36}
37
38/// Thread-local cache for frequently accessed services.
39///
40/// This provides ~8-10ns speedup for hot services by avoiding DashMap lookups.
41/// Uses a simple direct-mapped cache with TypeId + storage pointer as key.
42struct HotCache {
43 entries: [Option<CacheEntry>; HOT_CACHE_SLOTS],
44}
45
46impl HotCache {
47 const fn new() -> Self {
48 Self {
49 entries: [const { None }; HOT_CACHE_SLOTS],
50 }
51 }
52
53 /// Get a cached service if present for a specific container
54 ///
55 /// Phase 12+13 optimization: Uses UnsafeCell (no RefCell borrow check)
56 /// and pre-computed type_hash (no transmute on lookup).
57 #[inline(always)]
58 fn get<T: Send + Sync + 'static>(&self, storage_ptr: usize) -> Option<Arc<T>> {
59 let type_hash = Self::type_hash::<T>();
60 let slot = Self::slot_for_hash(type_hash, storage_ptr);
61
62 if let Some(entry) = &self.entries[slot] {
63 // Phase 13: Compare u64 hash directly (faster than TypeId comparison)
64 if entry.type_hash == type_hash && entry.storage_ptr == storage_ptr {
65 // Cache hit - clone and downcast (unchecked since type_hash matches)
66 // SAFETY: We verified type_hash matches, so the Arc contains type T
67 let arc = entry.service.clone();
68 return Some(unsafe { downcast_arc_unchecked(arc) });
69 }
70 }
71 None
72 }
73
74 /// Insert a service into the cache for a specific container
75 #[inline]
76 fn insert<T: Injectable>(&mut self, storage_ptr: usize, service: Arc<T>) {
77 let type_hash = Self::type_hash::<T>();
78 let slot = Self::slot_for_hash(type_hash, storage_ptr);
79
80 self.entries[slot] = Some(CacheEntry {
81 type_hash,
82 storage_ptr,
83 service: service as Arc<dyn Any + Send + Sync>,
84 });
85 }
86
87 /// Clear the cache (call when container is modified)
88 #[inline]
89 fn clear(&mut self) {
90 self.entries = [const { None }; HOT_CACHE_SLOTS];
91 }
92
93 /// Extract u64 hash from TypeId (computed once per type at compile time via monomorphization)
94 #[inline(always)]
95 fn type_hash<T: 'static>() -> u64 {
96 let type_id = TypeId::of::<T>();
97 // SAFETY: TypeId is #[repr(transparent)] wrapper around u128
98 unsafe { std::mem::transmute_copy(&type_id) }
99 }
100
101 /// Calculate slot index from pre-computed type hash and storage pointer
102 #[inline(always)]
103 fn slot_for_hash(type_hash: u64, storage_ptr: usize) -> usize {
104 // Fast bit mixing: XOR with rotated storage_ptr for good distribution
105 let mixed = type_hash ^ (storage_ptr as u64).rotate_left(32);
106
107 // Use golden ratio multiplication for final mixing (fast & good distribution)
108 let slot = mixed.wrapping_mul(0x9e3779b97f4a7c15);
109
110 (slot as usize) & (HOT_CACHE_SLOTS - 1)
111 }
112}
113
114thread_local! {
115 /// Thread-local hot cache for frequently accessed services
116 ///
117 /// Phase 12 optimization: Uses UnsafeCell instead of RefCell to eliminate
118 /// borrow checking overhead. This is safe because thread_local! guarantees
119 /// single-threaded access.
120 static HOT_CACHE: UnsafeCell<HotCache> = const { UnsafeCell::new(HotCache::new()) };
121}
122
123/// Helper to access the hot cache without RefCell overhead
124///
125/// SAFETY: thread_local! guarantees single-threaded access, so we can use
126/// UnsafeCell without data races. We ensure no aliasing by limiting access
127/// to immutable borrows for reads and brief mutable borrows for writes.
128#[inline(always)]
129fn with_hot_cache<F, R>(f: F) -> R
130where
131 F: FnOnce(&HotCache) -> R,
132{
133 HOT_CACHE.with(|cell| {
134 // SAFETY: thread_local guarantees single-threaded access
135 let cache = unsafe { &*cell.get() };
136 f(cache)
137 })
138}
139
140/// Helper to mutably access the hot cache
141#[inline(always)]
142fn with_hot_cache_mut<F, R>(f: F) -> R
143where
144 F: FnOnce(&mut HotCache) -> R,
145{
146 HOT_CACHE.with(|cell| {
147 // SAFETY: thread_local guarantees single-threaded access
148 let cache = unsafe { &mut *cell.get() };
149 f(cache)
150 })
151}
152
153/// High-performance dependency injection container.
154///
155/// Uses lock-free data structures for maximum concurrent throughput.
156/// Supports hierarchical scopes with full parent chain resolution.
157///
158/// # Examples
159///
160/// ```rust
161/// use dependency_injector::Container;
162///
163/// #[derive(Clone)]
164/// struct MyService { name: String }
165///
166/// let container = Container::new();
167/// container.singleton(MyService { name: "test".into() });
168///
169/// let service = container.get::<MyService>().unwrap();
170/// assert_eq!(service.name, "test");
171/// ```
172#[derive(Clone)]
173pub struct Container {
174 /// Service storage (lock-free)
175 storage: Arc<ServiceStorage>,
176 /// Parent storage - strong reference for fast resolution (Phase 2 optimization)
177 /// This avoids Weak::upgrade() cost on every parent resolution
178 parent_storage: Option<Arc<ServiceStorage>>,
179 /// Lock state - uses AtomicBool for fast lock checking (no contention)
180 locked: Arc<AtomicBool>,
181 /// Scope depth for debugging
182 depth: u32,
183}
184
185impl Container {
186 /// Create a new root container.
187 ///
188 /// # Examples
189 ///
190 /// ```rust
191 /// use dependency_injector::Container;
192 /// let container = Container::new();
193 /// ```
194 #[inline]
195 pub fn new() -> Self {
196 #[cfg(feature = "logging")]
197 debug!(
198 target: "dependency_injector",
199 depth = 0,
200 "Creating new root DI container"
201 );
202
203 Self {
204 storage: Arc::new(ServiceStorage::new()),
205 parent_storage: None,
206 locked: Arc::new(AtomicBool::new(false)),
207 depth: 0,
208 }
209 }
210
211 /// Create a container with pre-allocated capacity.
212 ///
213 /// Use this when you know approximately how many services will be registered.
214 #[inline]
215 pub fn with_capacity(capacity: usize) -> Self {
216 Self {
217 storage: Arc::new(ServiceStorage::with_capacity(capacity)),
218 parent_storage: None,
219 locked: Arc::new(AtomicBool::new(false)),
220 depth: 0,
221 }
222 }
223
224 /// Create a child scope that inherits from this container.
225 ///
226 /// Child scopes can:
227 /// - Access all services from parent scopes
228 /// - Override parent services with local registrations
229 /// - Have their own transient/scoped services
230 ///
231 /// # Examples
232 ///
233 /// ```rust
234 /// use dependency_injector::Container;
235 ///
236 /// #[derive(Clone)]
237 /// struct AppConfig { debug: bool }
238 ///
239 /// #[derive(Clone)]
240 /// struct RequestId(String);
241 ///
242 /// let root = Container::new();
243 /// root.singleton(AppConfig { debug: true });
244 ///
245 /// let request = root.scope();
246 /// request.singleton(RequestId("req-123".into()));
247 ///
248 /// // Request scope can access root config
249 /// assert!(request.contains::<AppConfig>());
250 /// ```
251 #[inline]
252 pub fn scope(&self) -> Self {
253 let child_depth = self.depth + 1;
254
255 #[cfg(feature = "logging")]
256 debug!(
257 target: "dependency_injector",
258 parent_depth = self.depth,
259 child_depth = child_depth,
260 parent_services = self.storage.len(),
261 "Creating child scope from parent container"
262 );
263
264 Self {
265 // Phase 9: Storage now holds parent reference for deep chain resolution
266 storage: Arc::new(ServiceStorage::with_parent(Arc::clone(&self.storage))),
267 parent_storage: Some(Arc::clone(&self.storage)), // Keep for quick parent access
268 locked: Arc::new(AtomicBool::new(false)),
269 depth: child_depth,
270 }
271 }
272
273 /// Alias for `scope()` - creates a child container.
274 #[inline]
275 pub fn create_scope(&self) -> Self {
276 self.scope()
277 }
278
279 // =========================================================================
280 // Registration Methods
281 // =========================================================================
282
283 /// Register a singleton service (eager).
284 ///
285 /// The instance is stored immediately and shared across all resolves.
286 ///
287 /// # Examples
288 ///
289 /// ```rust
290 /// use dependency_injector::Container;
291 ///
292 /// #[derive(Clone)]
293 /// struct Database { url: String }
294 ///
295 /// let container = Container::new();
296 /// container.singleton(Database { url: "postgres://localhost".into() });
297 /// ```
298 #[inline]
299 pub fn singleton<T: Injectable>(&self, instance: T) {
300 self.check_not_locked();
301
302 let type_id = TypeId::of::<T>();
303 let type_name = std::any::type_name::<T>();
304
305 #[cfg(feature = "logging")]
306 debug!(
307 target: "dependency_injector",
308 service = type_name,
309 lifetime = "singleton",
310 depth = self.depth,
311 service_count = self.storage.len() + 1,
312 "Registering singleton service"
313 );
314
315 // Phase 2: Use enum-based AnyFactory directly
316 self.storage
317 .insert(type_id, AnyFactory::singleton(instance));
318 }
319
320 /// Register a lazy singleton service.
321 ///
322 /// The factory is called once on first access, then the instance is cached.
323 ///
324 /// # Examples
325 ///
326 /// ```rust
327 /// use dependency_injector::Container;
328 ///
329 /// #[derive(Clone)]
330 /// struct ExpensiveService { data: Vec<u8> }
331 ///
332 /// let container = Container::new();
333 /// container.lazy(|| ExpensiveService {
334 /// data: vec![0; 1024 * 1024], // Only allocated on first use
335 /// });
336 /// ```
337 #[inline]
338 pub fn lazy<T: Injectable, F>(&self, factory: F)
339 where
340 F: Fn() -> T + Send + Sync + 'static,
341 {
342 self.check_not_locked();
343
344 let type_id = TypeId::of::<T>();
345 let type_name = std::any::type_name::<T>();
346
347 #[cfg(feature = "logging")]
348 debug!(
349 target: "dependency_injector",
350 service = type_name,
351 lifetime = "lazy_singleton",
352 depth = self.depth,
353 service_count = self.storage.len() + 1,
354 "Registering lazy singleton service (will be created on first access)"
355 );
356
357 // Phase 2: Use enum-based AnyFactory directly
358 self.storage.insert(type_id, AnyFactory::lazy(factory));
359 }
360
361 /// Register a transient service.
362 ///
363 /// A new instance is created on every resolve.
364 ///
365 /// # Examples
366 ///
367 /// ```rust
368 /// use dependency_injector::Container;
369 /// use std::sync::atomic::{AtomicU64, Ordering};
370 ///
371 /// static COUNTER: AtomicU64 = AtomicU64::new(0);
372 ///
373 /// #[derive(Clone)]
374 /// struct RequestId(u64);
375 ///
376 /// let container = Container::new();
377 /// container.transient(|| RequestId(COUNTER.fetch_add(1, Ordering::SeqCst)));
378 ///
379 /// let id1 = container.get::<RequestId>().unwrap();
380 /// let id2 = container.get::<RequestId>().unwrap();
381 /// assert_ne!(id1.0, id2.0); // Different instances
382 /// ```
383 #[inline]
384 pub fn transient<T: Injectable, F>(&self, factory: F)
385 where
386 F: Fn() -> T + Send + Sync + 'static,
387 {
388 self.check_not_locked();
389
390 let type_id = TypeId::of::<T>();
391 let type_name = std::any::type_name::<T>();
392
393 #[cfg(feature = "logging")]
394 debug!(
395 target: "dependency_injector",
396 service = type_name,
397 lifetime = "transient",
398 depth = self.depth,
399 service_count = self.storage.len() + 1,
400 "Registering transient service (new instance on every resolve)"
401 );
402
403 // Phase 2: Use enum-based AnyFactory directly
404 self.storage.insert(type_id, AnyFactory::transient(factory));
405 }
406
407 /// Register using a factory (alias for `lazy`).
408 #[inline]
409 pub fn register_factory<T: Injectable, F>(&self, factory: F)
410 where
411 F: Fn() -> T + Send + Sync + 'static,
412 {
413 self.lazy(factory);
414 }
415
416 /// Register an instance (alias for `singleton`).
417 #[inline]
418 pub fn register<T: Injectable>(&self, instance: T) {
419 self.singleton(instance);
420 }
421
422 /// Register a boxed instance.
423 #[inline]
424 #[allow(clippy::boxed_local)]
425 pub fn register_boxed<T: Injectable>(&self, instance: Box<T>) {
426 self.singleton(*instance);
427 }
428
429 /// Register by TypeId directly (advanced use).
430 #[inline]
431 pub fn register_by_id(&self, type_id: TypeId, instance: Arc<dyn Any + Send + Sync>) {
432 self.check_not_locked();
433
434 // Phase 2: Use the singleton factory with pre-erased Arc directly
435 self.storage.insert(
436 type_id,
437 AnyFactory::Singleton(crate::factory::SingletonFactory { instance }),
438 );
439 }
440
441 // =========================================================================
442 // Resolution Methods
443 // =========================================================================
444
445 /// Resolve a service by type.
446 ///
447 /// Returns `Arc<T>` for zero-copy sharing. Walks the parent chain if
448 /// not found in the current scope.
449 ///
450 /// # Performance
451 ///
452 /// Uses thread-local caching for frequently accessed services (~8ns vs ~19ns).
453 /// The cache is automatically populated on first access.
454 ///
455 /// # Examples
456 ///
457 /// ```rust
458 /// use dependency_injector::Container;
459 ///
460 /// #[derive(Clone)]
461 /// struct MyService;
462 ///
463 /// let container = Container::new();
464 /// container.singleton(MyService);
465 ///
466 /// let service = container.get::<MyService>().unwrap();
467 /// ```
468 #[inline]
469 pub fn get<T: Injectable>(&self) -> Result<Arc<T>> {
470 // Get storage pointer for cache key (unique per container scope)
471 let storage_ptr = Arc::as_ptr(&self.storage) as usize;
472
473 // Phase 5+12: Check thread-local hot cache first (UnsafeCell, no RefCell overhead)
474 // Note: Transients won't be in cache, so they'll fall through to get_and_cache
475 if let Some(cached) = with_hot_cache(|cache| cache.get::<T>(storage_ptr)) {
476 #[cfg(feature = "logging")]
477 trace!(
478 target: "dependency_injector",
479 service = std::any::type_name::<T>(),
480 depth = self.depth,
481 location = "hot_cache",
482 "Service resolved from thread-local cache"
483 );
484 return Ok(cached);
485 }
486
487 // Cache miss - resolve normally and cache the result (unless transient)
488 self.get_and_cache::<T>(storage_ptr)
489 }
490
491 /// Internal: Resolve and cache a service
492 ///
493 /// Phase 15 optimization: Fast path for root containers (depth == 0) avoids
494 /// function call overhead to resolve_from_parents when there are no parents.
495 #[inline]
496 fn get_and_cache<T: Injectable>(&self, storage_ptr: usize) -> Result<Arc<T>> {
497 let type_id = TypeId::of::<T>();
498
499 #[cfg(feature = "logging")]
500 let type_name = std::any::type_name::<T>();
501
502 #[cfg(feature = "logging")]
503 trace!(
504 target: "dependency_injector",
505 service = type_name,
506 depth = self.depth,
507 "Resolving service (cache miss)"
508 );
509
510 // Try local storage first (most common case)
511 // Use get_with_transient_flag to avoid second DashMap lookup for is_transient
512 if let Some((service, is_transient)) = self.storage.get_with_transient_flag::<T>() {
513 #[cfg(feature = "logging")]
514 trace!(
515 target: "dependency_injector",
516 service = type_name,
517 depth = self.depth,
518 location = "local",
519 "Service resolved from current scope"
520 );
521
522 // Cache non-transient services (transients create new instances each time)
523 if !is_transient {
524 with_hot_cache_mut(|cache| cache.insert(storage_ptr, Arc::clone(&service)));
525 }
526
527 return Ok(service);
528 }
529
530 // Phase 15: Fast path for root containers - no parents to walk
531 if self.depth == 0 {
532 #[cfg(feature = "logging")]
533 debug!(
534 target: "dependency_injector",
535 service = std::any::type_name::<T>(),
536 "Service not found in root container"
537 );
538 return Err(DiError::not_found::<T>());
539 }
540
541 // Walk parent chain (cold path)
542 self.resolve_from_parents::<T>(&type_id, storage_ptr)
543 }
544
545 /// Resolve from parent chain (internal)
546 ///
547 /// Phase 9 optimization: Walks the full parent chain via ServiceStorage.parent.
548 /// This allows services to be resolved from any ancestor scope.
549 ///
550 /// Phase 14 optimization: Marked as cold to improve branch prediction in the
551 /// hot path - most resolutions hit the cache and don't need parent traversal.
552 #[cold]
553 fn resolve_from_parents<T: Injectable>(
554 &self,
555 type_id: &TypeId,
556 storage_ptr: usize,
557 ) -> Result<Arc<T>> {
558 let type_name = std::any::type_name::<T>();
559
560 #[cfg(feature = "logging")]
561 trace!(
562 target: "dependency_injector",
563 service = type_name,
564 depth = self.depth,
565 "Service not in local scope, walking parent chain"
566 );
567
568 // Walk the full parent chain via storage's parent references
569 let mut current = self.storage.parent();
570 let mut ancestor_depth = self.depth.saturating_sub(1);
571
572 while let Some(storage) = current {
573 if let Some(arc) = storage.resolve(type_id) {
574 // SAFETY: We resolved by TypeId::of::<T>(), so the factory
575 // was registered with the same TypeId and stores type T.
576 let typed: Arc<T> = unsafe { downcast_arc_unchecked(arc) };
577
578 #[cfg(feature = "logging")]
579 trace!(
580 target: "dependency_injector",
581 service = type_name,
582 depth = self.depth,
583 ancestor_depth = ancestor_depth,
584 location = "ancestor",
585 "Service resolved from ancestor scope"
586 );
587
588 // Cache non-transient services from parent (using child's storage ptr as key)
589 if !storage.is_transient(type_id) {
590 with_hot_cache_mut(|cache| cache.insert(storage_ptr, Arc::clone(&typed)));
591 }
592
593 return Ok(typed);
594 }
595 current = storage.parent();
596 ancestor_depth = ancestor_depth.saturating_sub(1);
597 }
598
599 #[cfg(feature = "logging")]
600 debug!(
601 target: "dependency_injector",
602 service = type_name,
603 depth = self.depth,
604 "Service not found in container or parent chain"
605 );
606
607 Err(DiError::not_found::<T>())
608 }
609
610 /// Clear the thread-local hot cache.
611 ///
612 /// Call this after modifying the container (registering/removing services)
613 /// if you want subsequent resolutions to see the changes immediately.
614 ///
615 /// Note: The cache is automatically invalidated when services are
616 /// re-registered, but this method can be used for explicit control.
617 #[inline]
618 pub fn clear_cache(&self) {
619 with_hot_cache_mut(|cache| cache.clear());
620 }
621
622 /// Pre-warm the thread-local cache with a specific service type.
623 ///
624 /// This can be useful at the start of request handling to ensure
625 /// hot services are already in the cache.
626 ///
627 /// # Example
628 ///
629 /// ```rust
630 /// use dependency_injector::Container;
631 ///
632 /// #[derive(Clone)]
633 /// struct Database;
634 ///
635 /// let container = Container::new();
636 /// container.singleton(Database);
637 ///
638 /// // Pre-warm cache for hot services
639 /// container.warm_cache::<Database>();
640 /// ```
641 #[inline]
642 pub fn warm_cache<T: Injectable>(&self) {
643 // Simply resolve the service to populate the cache
644 let _ = self.get::<T>();
645 }
646
647 /// Alias for `get` - resolve a service.
648 #[inline]
649 pub fn resolve<T: Injectable>(&self) -> Result<Arc<T>> {
650 self.get::<T>()
651 }
652
653 /// Try to resolve, returning None if not found.
654 ///
655 /// # Examples
656 ///
657 /// ```rust
658 /// use dependency_injector::Container;
659 ///
660 /// #[derive(Clone)]
661 /// struct OptionalService;
662 ///
663 /// let container = Container::new();
664 /// assert!(container.try_get::<OptionalService>().is_none());
665 /// ```
666 #[inline]
667 pub fn try_get<T: Injectable>(&self) -> Option<Arc<T>> {
668 self.get::<T>().ok()
669 }
670
671 /// Alias for `try_get`.
672 #[inline]
673 pub fn try_resolve<T: Injectable>(&self) -> Option<Arc<T>> {
674 self.try_get::<T>()
675 }
676
677 // =========================================================================
678 // Query Methods
679 // =========================================================================
680
681 /// Check if a service is registered.
682 ///
683 /// Checks both current scope and parent scopes.
684 #[inline]
685 pub fn contains<T: Injectable>(&self) -> bool {
686 let type_id = TypeId::of::<T>();
687 self.contains_type_id(&type_id)
688 }
689
690 /// Alias for `contains`.
691 #[inline]
692 pub fn has<T: Injectable>(&self) -> bool {
693 self.contains::<T>()
694 }
695
696 /// Check by TypeId
697 /// Phase 9 optimization: Uses storage's parent chain for deep hierarchy support
698 fn contains_type_id(&self, type_id: &TypeId) -> bool {
699 // Check local storage and full parent chain
700 self.storage.contains_in_chain(type_id)
701 }
702
703 /// Get the number of services in this scope (not including parents).
704 #[inline]
705 pub fn len(&self) -> usize {
706 self.storage.len()
707 }
708
709 /// Check if this scope is empty.
710 #[inline]
711 pub fn is_empty(&self) -> bool {
712 self.storage.is_empty()
713 }
714
715 /// Get all registered TypeIds in this scope.
716 pub fn registered_types(&self) -> Vec<TypeId> {
717 self.storage.type_ids()
718 }
719
720 /// Get the scope depth (0 = root).
721 #[inline]
722 pub fn depth(&self) -> u32 {
723 self.depth
724 }
725
726 // =========================================================================
727 // Lifecycle Methods
728 // =========================================================================
729
730 /// Lock the container to prevent further registrations.
731 ///
732 /// Useful for ensuring no services are registered after app initialization.
733 #[inline]
734 pub fn lock(&self) {
735 self.locked.store(true, Ordering::Release);
736
737 #[cfg(feature = "logging")]
738 debug!(
739 target: "dependency_injector",
740 depth = self.depth,
741 service_count = self.storage.len(),
742 "Container locked - no further registrations allowed"
743 );
744 }
745
746 /// Check if the container is locked.
747 #[inline]
748 pub fn is_locked(&self) -> bool {
749 self.locked.load(Ordering::Acquire)
750 }
751
752 /// Freeze the container into an immutable, perfectly-hashed storage.
753 ///
754 /// This creates a `FrozenStorage` that uses minimal perfect hashing for
755 /// O(1) lookups without hash collisions, providing ~5ns faster resolution.
756 ///
757 /// Note: This also locks the container to prevent further registrations.
758 ///
759 /// # Example
760 ///
761 /// ```rust,ignore
762 /// use dependency_injector::Container;
763 ///
764 /// let container = Container::new();
765 /// container.singleton(MyService { ... });
766 ///
767 /// let frozen = container.freeze();
768 /// // Use frozen.resolve(&type_id) for faster lookups
769 /// ```
770 #[cfg(feature = "perfect-hash")]
771 #[inline]
772 pub fn freeze(&self) -> crate::storage::FrozenStorage {
773 self.lock();
774 crate::storage::FrozenStorage::from_storage(&self.storage)
775 }
776
777 /// Clear all services from this scope.
778 ///
779 /// Does not affect parent scopes.
780 #[inline]
781 pub fn clear(&self) {
782 let count = self.storage.len();
783 self.storage.clear();
784
785 #[cfg(feature = "logging")]
786 debug!(
787 target: "dependency_injector",
788 depth = self.depth,
789 services_removed = count,
790 "Container cleared - all services removed from this scope"
791 );
792 }
793
794 /// Panic if locked (internal helper).
795 /// Uses relaxed ordering for fast path - we only need eventual consistency
796 /// since registration is not a hot path and locking is rare.
797 #[inline]
798 fn check_not_locked(&self) {
799 if self.locked.load(Ordering::Relaxed) {
800 panic!("Cannot register services: container is locked");
801 }
802 }
803
804 // =========================================================================
805 // Batch Registration (Phase 3)
806 // =========================================================================
807
808 /// Register multiple services in a single batch operation.
809 ///
810 /// This is more efficient than individual registrations when registering
811 /// many services at once, as it:
812 /// - Performs a single lock check at the start
813 /// - Minimizes per-call overhead
814 ///
815 /// # Examples
816 ///
817 /// ```rust
818 /// use dependency_injector::Container;
819 ///
820 /// #[derive(Clone)]
821 /// struct Database { url: String }
822 /// #[derive(Clone)]
823 /// struct Cache { size: usize }
824 /// #[derive(Clone)]
825 /// struct Logger { level: String }
826 ///
827 /// let container = Container::new();
828 /// container.batch(|batch| {
829 /// batch.singleton(Database { url: "postgres://localhost".into() });
830 /// batch.singleton(Cache { size: 1024 });
831 /// batch.singleton(Logger { level: "info".into() });
832 /// });
833 ///
834 /// assert!(container.contains::<Database>());
835 /// assert!(container.contains::<Cache>());
836 /// assert!(container.contains::<Logger>());
837 /// ```
838 ///
839 /// Note: For maximum performance with many services, prefer the builder API:
840 /// ```rust
841 /// use dependency_injector::Container;
842 ///
843 /// #[derive(Clone)]
844 /// struct A;
845 /// #[derive(Clone)]
846 /// struct B;
847 ///
848 /// let container = Container::new();
849 /// container.register_batch()
850 /// .singleton(A)
851 /// .singleton(B)
852 /// .done();
853 /// ```
854 #[inline]
855 pub fn batch<F>(&self, f: F)
856 where
857 F: FnOnce(BatchRegistrar<'_>),
858 {
859 self.check_not_locked();
860
861 #[cfg(feature = "logging")]
862 let start_count = self.storage.len();
863
864 // Create a zero-cost batch registrar that wraps the storage
865 f(BatchRegistrar {
866 storage: &self.storage,
867 });
868
869 #[cfg(feature = "logging")]
870 {
871 let end_count = self.storage.len();
872 debug!(
873 target: "dependency_injector",
874 depth = self.depth,
875 services_registered = end_count - start_count,
876 "Batch registration completed"
877 );
878 }
879 }
880
881 /// Start a fluent batch registration.
882 ///
883 /// This is faster than the closure-based `batch()` for many services
884 /// because it avoids closure overhead.
885 ///
886 /// # Example
887 ///
888 /// ```rust
889 /// use dependency_injector::Container;
890 ///
891 /// #[derive(Clone)]
892 /// struct Database { url: String }
893 /// #[derive(Clone)]
894 /// struct Cache { size: usize }
895 ///
896 /// let container = Container::new();
897 /// container.register_batch()
898 /// .singleton(Database { url: "postgres://localhost".into() })
899 /// .singleton(Cache { size: 1024 })
900 /// .done();
901 ///
902 /// assert!(container.contains::<Database>());
903 /// assert!(container.contains::<Cache>());
904 /// ```
905 #[inline]
906 pub fn register_batch(&self) -> BatchBuilder<'_> {
907 self.check_not_locked();
908 BatchBuilder {
909 storage: &self.storage,
910 #[cfg(feature = "logging")]
911 count: 0,
912 }
913 }
914}
915
916/// Fluent batch registration builder.
917///
918/// Provides a chainable API for registering multiple services without closure overhead.
919pub struct BatchBuilder<'a> {
920 storage: &'a ServiceStorage,
921 #[cfg(feature = "logging")]
922 count: usize,
923}
924
925impl<'a> BatchBuilder<'a> {
926 /// Register a singleton and continue the chain
927 #[inline]
928 pub fn singleton<T: Injectable>(self, instance: T) -> Self {
929 self.storage
930 .insert(TypeId::of::<T>(), AnyFactory::singleton(instance));
931 Self {
932 storage: self.storage,
933 #[cfg(feature = "logging")]
934 count: self.count + 1,
935 }
936 }
937
938 /// Register a lazy singleton and continue the chain
939 #[inline]
940 pub fn lazy<T: Injectable, F>(self, factory: F) -> Self
941 where
942 F: Fn() -> T + Send + Sync + 'static,
943 {
944 self.storage
945 .insert(TypeId::of::<T>(), AnyFactory::lazy(factory));
946 Self {
947 storage: self.storage,
948 #[cfg(feature = "logging")]
949 count: self.count + 1,
950 }
951 }
952
953 /// Register a transient and continue the chain
954 #[inline]
955 pub fn transient<T: Injectable, F>(self, factory: F) -> Self
956 where
957 F: Fn() -> T + Send + Sync + 'static,
958 {
959 self.storage
960 .insert(TypeId::of::<T>(), AnyFactory::transient(factory));
961 Self {
962 storage: self.storage,
963 #[cfg(feature = "logging")]
964 count: self.count + 1,
965 }
966 }
967
968 /// Finish the batch registration
969 #[inline]
970 pub fn done(self) {
971 #[cfg(feature = "logging")]
972 debug!(
973 target: "dependency_injector",
974 services_registered = self.count,
975 "Batch registration completed"
976 );
977 }
978}
979
980/// Batch registrar for closure-based bulk registration.
981///
982/// A zero-cost wrapper that provides direct storage access.
983/// The lock check is done once in `Container::batch()`.
984#[repr(transparent)]
985pub struct BatchRegistrar<'a> {
986 storage: &'a ServiceStorage,
987}
988
989impl<'a> BatchRegistrar<'a> {
990 /// Register a singleton service (inserted immediately)
991 #[inline]
992 pub fn singleton<T: Injectable>(&self, instance: T) {
993 self.storage
994 .insert(TypeId::of::<T>(), AnyFactory::singleton(instance));
995 }
996
997 /// Register a lazy singleton service (inserted immediately)
998 #[inline]
999 pub fn lazy<T: Injectable, F>(&self, factory: F)
1000 where
1001 F: Fn() -> T + Send + Sync + 'static,
1002 {
1003 self.storage
1004 .insert(TypeId::of::<T>(), AnyFactory::lazy(factory));
1005 }
1006
1007 /// Register a transient service (inserted immediately)
1008 #[inline]
1009 pub fn transient<T: Injectable, F>(&self, factory: F)
1010 where
1011 F: Fn() -> T + Send + Sync + 'static,
1012 {
1013 self.storage
1014 .insert(TypeId::of::<T>(), AnyFactory::transient(factory));
1015 }
1016}
1017
1018// =============================================================================
1019// Scope Pooling (Phase 6 optimization)
1020// =============================================================================
1021
1022use std::sync::Mutex;
1023
1024/// A pool of pre-allocated scopes for high-throughput scenarios.
1025///
1026/// Creating a scope involves allocating a DashMap (~134ns). For web servers
1027/// handling thousands of requests per second, this adds up. ScopePool pre-allocates
1028/// scopes and reuses them, reducing per-request overhead to near-zero.
1029///
1030/// # Example
1031///
1032/// ```rust
1033/// use dependency_injector::{Container, ScopePool};
1034///
1035/// #[derive(Clone)]
1036/// struct AppConfig { name: String }
1037///
1038/// #[derive(Clone)]
1039/// struct RequestId(String);
1040///
1041/// // Create root container with app-wide services
1042/// let root = Container::new();
1043/// root.singleton(AppConfig { name: "MyApp".into() });
1044///
1045/// // Create a pool of reusable scopes (pre-allocates 4 scopes)
1046/// let pool = ScopePool::new(&root, 4);
1047///
1048/// // In request handler: acquire a pooled scope
1049/// {
1050/// let scope = pool.acquire();
1051/// scope.singleton(RequestId("req-123".into()));
1052///
1053/// // Can access parent services
1054/// assert!(scope.contains::<AppConfig>());
1055/// assert!(scope.contains::<RequestId>());
1056///
1057/// // Scope automatically released when dropped
1058/// }
1059///
1060/// // Next request reuses the same scope allocation
1061/// {
1062/// let scope = pool.acquire();
1063/// // Previous RequestId is cleared, fresh scope
1064/// assert!(!scope.contains::<RequestId>());
1065/// }
1066/// ```
1067///
1068/// # Performance
1069///
1070/// - First acquisition: ~134ns (creates new scope if pool is empty)
1071/// - Subsequent acquisitions: ~20ns (reuses pooled scope)
1072/// - Release: ~10ns (clears and returns to pool)
1073pub struct ScopePool {
1074 /// Parent storage to create scopes from
1075 parent_storage: Arc<ServiceStorage>,
1076 /// Pool of available scopes (storage + lock state pairs)
1077 available: Mutex<Vec<ScopeSlot>>,
1078 /// Parent depth for child scope depth calculation
1079 parent_depth: u32,
1080}
1081
1082/// A reusable scope slot containing pre-allocated storage and lock state
1083struct ScopeSlot {
1084 /// Pre-allocated storage with parent reference
1085 storage: Arc<ServiceStorage>,
1086 locked: Arc<AtomicBool>,
1087}
1088
1089impl ScopePool {
1090 /// Create a new scope pool with pre-allocated capacity.
1091 ///
1092 /// # Arguments
1093 ///
1094 /// * `parent` - The parent container that scopes will inherit from
1095 /// * `capacity` - Number of scopes to pre-allocate
1096 ///
1097 /// # Example
1098 ///
1099 /// ```rust
1100 /// use dependency_injector::{Container, ScopePool};
1101 ///
1102 /// let root = Container::new();
1103 /// // Pre-allocate 8 scopes for concurrent request handling
1104 /// let pool = ScopePool::new(&root, 8);
1105 /// ```
1106 pub fn new(parent: &Container, capacity: usize) -> Self {
1107 let mut available = Vec::with_capacity(capacity);
1108
1109 // Pre-allocate storage with parent reference and lock states
1110 for _ in 0..capacity {
1111 available.push(ScopeSlot {
1112 storage: Arc::new(ServiceStorage::with_parent(Arc::clone(&parent.storage))),
1113 locked: Arc::new(AtomicBool::new(false)),
1114 });
1115 }
1116
1117 #[cfg(feature = "logging")]
1118 debug!(
1119 target: "dependency_injector",
1120 capacity = capacity,
1121 parent_depth = parent.depth,
1122 "Created scope pool with pre-allocated scopes"
1123 );
1124
1125 Self {
1126 parent_storage: Arc::clone(&parent.storage),
1127 available: Mutex::new(available),
1128 parent_depth: parent.depth,
1129 }
1130 }
1131
1132 /// Acquire a scope from the pool.
1133 ///
1134 /// Returns a `PooledScope` that automatically returns to the pool when dropped.
1135 /// If the pool is empty, creates a new scope.
1136 ///
1137 /// # Example
1138 ///
1139 /// ```rust
1140 /// use dependency_injector::{Container, ScopePool};
1141 ///
1142 /// #[derive(Clone)]
1143 /// struct RequestData { id: u64 }
1144 ///
1145 /// let root = Container::new();
1146 /// let pool = ScopePool::new(&root, 4);
1147 ///
1148 /// let scope = pool.acquire();
1149 /// scope.singleton(RequestData { id: 123 });
1150 /// let data = scope.get::<RequestData>().unwrap();
1151 /// assert_eq!(data.id, 123);
1152 /// ```
1153 #[inline]
1154 pub fn acquire(&self) -> PooledScope<'_> {
1155 let slot = self.available.lock().unwrap().pop();
1156
1157 let (storage, locked) = match slot {
1158 Some(slot) => {
1159 #[cfg(feature = "logging")]
1160 trace!(
1161 target: "dependency_injector",
1162 "Acquired scope from pool (reusing storage)"
1163 );
1164 (slot.storage, slot.locked)
1165 }
1166 None => {
1167 #[cfg(feature = "logging")]
1168 trace!(
1169 target: "dependency_injector",
1170 "Pool empty, creating new scope"
1171 );
1172 (
1173 Arc::new(ServiceStorage::with_parent(Arc::clone(
1174 &self.parent_storage,
1175 ))),
1176 Arc::new(AtomicBool::new(false)),
1177 )
1178 }
1179 };
1180
1181 let container = Container {
1182 storage,
1183 parent_storage: Some(Arc::clone(&self.parent_storage)),
1184 locked,
1185 depth: self.parent_depth + 1,
1186 };
1187
1188 PooledScope {
1189 container: Some(container),
1190 pool: self,
1191 }
1192 }
1193
1194 /// Return a scope to the pool (internal use).
1195 #[inline]
1196 fn release(&self, container: Container) {
1197 // Clear storage for reuse (parent reference is preserved)
1198 container.storage.clear();
1199 // Reset lock state
1200 container.locked.store(false, Ordering::Relaxed);
1201
1202 // Return to pool
1203 self.available.lock().unwrap().push(ScopeSlot {
1204 storage: container.storage,
1205 locked: container.locked,
1206 });
1207
1208 #[cfg(feature = "logging")]
1209 trace!(
1210 target: "dependency_injector",
1211 "Released scope back to pool"
1212 );
1213 }
1214
1215 /// Get the current number of available scopes in the pool.
1216 #[inline]
1217 pub fn available_count(&self) -> usize {
1218 self.available.lock().unwrap().len()
1219 }
1220}
1221
1222/// A scope acquired from a pool that automatically returns when dropped.
1223///
1224/// This provides RAII-style management of pooled scopes, ensuring they're
1225/// always returned to the pool even if the code panics.
1226pub struct PooledScope<'a> {
1227 container: Option<Container>,
1228 pool: &'a ScopePool,
1229}
1230
1231impl PooledScope<'_> {
1232 /// Get a reference to the underlying container.
1233 #[inline]
1234 pub fn container(&self) -> &Container {
1235 self.container.as_ref().unwrap()
1236 }
1237}
1238
1239impl std::ops::Deref for PooledScope<'_> {
1240 type Target = Container;
1241
1242 #[inline]
1243 fn deref(&self) -> &Self::Target {
1244 self.container.as_ref().unwrap()
1245 }
1246}
1247
1248impl Drop for PooledScope<'_> {
1249 fn drop(&mut self) {
1250 if let Some(container) = self.container.take() {
1251 self.pool.release(container);
1252 }
1253 }
1254}
1255
1256impl Default for Container {
1257 fn default() -> Self {
1258 Self::new()
1259 }
1260}
1261
1262impl std::fmt::Debug for Container {
1263 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1264 f.debug_struct("Container")
1265 .field("service_count", &self.len())
1266 .field("depth", &self.depth)
1267 .field("has_parent", &self.parent_storage.is_some())
1268 .field("locked", &self.is_locked())
1269 .finish_non_exhaustive()
1270 }
1271}
1272
1273// =========================================================================
1274// Thread Safety
1275// =========================================================================
1276
1277// Container is Send + Sync because:
1278// - ServiceStorage uses DashMap (thread-safe)
1279// - parent is Weak<...> which is Send + Sync
1280// - locked uses AtomicBool (Send + Sync)
1281unsafe impl Send for Container {}
1282unsafe impl Sync for Container {}
1283
1284#[cfg(test)]
1285mod tests {
1286 use super::*;
1287
1288 #[derive(Clone)]
1289 struct TestService {
1290 value: String,
1291 }
1292
1293 #[allow(dead_code)]
1294 #[derive(Clone)]
1295 struct AnotherService {
1296 name: String,
1297 }
1298
1299 #[test]
1300 fn test_singleton() {
1301 let container = Container::new();
1302 container.singleton(TestService {
1303 value: "test".into(),
1304 });
1305
1306 let s1 = container.get::<TestService>().unwrap();
1307 let s2 = container.get::<TestService>().unwrap();
1308
1309 assert_eq!(s1.value, "test");
1310 assert!(Arc::ptr_eq(&s1, &s2));
1311 }
1312
1313 #[test]
1314 fn test_lazy() {
1315 use std::sync::atomic::{AtomicBool, Ordering};
1316
1317 static CREATED: AtomicBool = AtomicBool::new(false);
1318
1319 let container = Container::new();
1320 container.lazy(|| {
1321 CREATED.store(true, Ordering::SeqCst);
1322 TestService {
1323 value: "lazy".into(),
1324 }
1325 });
1326
1327 assert!(!CREATED.load(Ordering::SeqCst));
1328
1329 let s = container.get::<TestService>().unwrap();
1330 assert!(CREATED.load(Ordering::SeqCst));
1331 assert_eq!(s.value, "lazy");
1332 }
1333
1334 #[test]
1335 fn test_transient() {
1336 use std::sync::atomic::{AtomicU32, Ordering};
1337
1338 static COUNTER: AtomicU32 = AtomicU32::new(0);
1339
1340 #[derive(Clone)]
1341 struct Counter(u32);
1342
1343 let container = Container::new();
1344 container.transient(|| Counter(COUNTER.fetch_add(1, Ordering::SeqCst)));
1345
1346 let c1 = container.get::<Counter>().unwrap();
1347 let c2 = container.get::<Counter>().unwrap();
1348
1349 assert_ne!(c1.0, c2.0);
1350 }
1351
1352 #[test]
1353 fn test_scope_inheritance() {
1354 let root = Container::new();
1355 root.singleton(TestService {
1356 value: "root".into(),
1357 });
1358
1359 let child = root.scope();
1360 child.singleton(AnotherService {
1361 name: "child".into(),
1362 });
1363
1364 // Child sees both
1365 assert!(child.contains::<TestService>());
1366 assert!(child.contains::<AnotherService>());
1367
1368 // Root only sees its own
1369 assert!(root.contains::<TestService>());
1370 assert!(!root.contains::<AnotherService>());
1371 }
1372
1373 #[test]
1374 fn test_scope_override() {
1375 let root = Container::new();
1376 root.singleton(TestService {
1377 value: "root".into(),
1378 });
1379
1380 let child = root.scope();
1381 child.singleton(TestService {
1382 value: "child".into(),
1383 });
1384
1385 let root_service = root.get::<TestService>().unwrap();
1386 let child_service = child.get::<TestService>().unwrap();
1387
1388 assert_eq!(root_service.value, "root");
1389 assert_eq!(child_service.value, "child");
1390 }
1391
1392 #[test]
1393 fn test_not_found() {
1394 let container = Container::new();
1395 let result = container.get::<TestService>();
1396 assert!(result.is_err());
1397 }
1398
1399 #[test]
1400 fn test_lock() {
1401 let container = Container::new();
1402 assert!(!container.is_locked());
1403
1404 container.lock();
1405 assert!(container.is_locked());
1406 }
1407
1408 #[test]
1409 #[should_panic(expected = "Cannot register services: container is locked")]
1410 fn test_register_after_lock() {
1411 let container = Container::new();
1412 container.lock();
1413 container.singleton(TestService {
1414 value: "fail".into(),
1415 });
1416 }
1417
1418 #[test]
1419 fn test_batch_registration() {
1420 #[derive(Clone)]
1421 struct ServiceA(i32);
1422 #[allow(dead_code)]
1423 #[derive(Clone)]
1424 struct ServiceB(String);
1425
1426 let container = Container::new();
1427 container.batch(|batch| {
1428 batch.singleton(ServiceA(42));
1429 batch.singleton(ServiceB("test".into()));
1430 batch.lazy(|| TestService {
1431 value: "lazy".into(),
1432 });
1433 });
1434
1435 assert!(container.contains::<ServiceA>());
1436 assert!(container.contains::<ServiceB>());
1437 assert!(container.contains::<TestService>());
1438
1439 let a = container.get::<ServiceA>().unwrap();
1440 assert_eq!(a.0, 42);
1441 }
1442
1443 #[test]
1444 fn test_scope_pool_basic() {
1445 #[derive(Clone)]
1446 struct RequestId(u64);
1447
1448 let root = Container::new();
1449 root.singleton(TestService {
1450 value: "root".into(),
1451 });
1452
1453 // Create pool with 2 pre-allocated scopes
1454 let pool = ScopePool::new(&root, 2);
1455 assert_eq!(pool.available_count(), 2);
1456
1457 // Acquire a scope
1458 {
1459 let scope = pool.acquire();
1460 assert_eq!(pool.available_count(), 1);
1461
1462 // Can access parent services
1463 assert!(scope.contains::<TestService>());
1464
1465 // Register request-specific service
1466 scope.singleton(RequestId(123));
1467 assert!(scope.contains::<RequestId>());
1468
1469 let id = scope.get::<RequestId>().unwrap();
1470 assert_eq!(id.0, 123);
1471 }
1472 // Scope released back to pool
1473 assert_eq!(pool.available_count(), 2);
1474 }
1475
1476 #[test]
1477 fn test_scope_pool_reuse() {
1478 #[derive(Clone)]
1479 struct RequestId(u64);
1480
1481 let root = Container::new();
1482 let pool = ScopePool::new(&root, 1);
1483
1484 // First request
1485 {
1486 let scope = pool.acquire();
1487 scope.singleton(RequestId(1));
1488 assert!(scope.contains::<RequestId>());
1489 }
1490
1491 // Second request - should reuse the same scope (cleared)
1492 {
1493 let scope = pool.acquire();
1494 // Previous RequestId should be cleared
1495 assert!(!scope.contains::<RequestId>());
1496
1497 scope.singleton(RequestId(2));
1498 let id = scope.get::<RequestId>().unwrap();
1499 assert_eq!(id.0, 2);
1500 }
1501 }
1502
1503 #[test]
1504 fn test_scope_pool_expansion() {
1505 let root = Container::new();
1506 let pool = ScopePool::new(&root, 1);
1507
1508 // Acquire more scopes than pre-allocated
1509 let _s1 = pool.acquire();
1510 let _s2 = pool.acquire(); // Creates new scope
1511
1512 assert_eq!(pool.available_count(), 0);
1513
1514 // Both should work
1515 drop(_s1);
1516 drop(_s2);
1517
1518 // Both return to pool
1519 assert_eq!(pool.available_count(), 2);
1520 }
1521
1522 #[test]
1523 fn test_deep_parent_chain() {
1524 // Test that services can be resolved from grandparent and beyond
1525 #[derive(Clone)]
1526 struct RootService(i32);
1527 #[derive(Clone)]
1528 struct MiddleService(i32);
1529 #[derive(Clone)]
1530 struct LeafService(i32);
1531
1532 // Create 4-level hierarchy: root -> middle1 -> middle2 -> leaf
1533 let root = Container::new();
1534 root.singleton(RootService(1));
1535
1536 let middle1 = root.scope();
1537 middle1.singleton(MiddleService(2));
1538
1539 let middle2 = middle1.scope();
1540 // No service in middle2
1541
1542 let leaf = middle2.scope();
1543 leaf.singleton(LeafService(4));
1544
1545 // Leaf should be able to access all ancestor services
1546 assert!(
1547 leaf.contains::<RootService>(),
1548 "Should find root service in leaf"
1549 );
1550 assert!(
1551 leaf.contains::<MiddleService>(),
1552 "Should find middle service in leaf"
1553 );
1554 assert!(
1555 leaf.contains::<LeafService>(),
1556 "Should find leaf service in leaf"
1557 );
1558
1559 // Verify resolution works
1560 let root_svc = leaf.get::<RootService>().unwrap();
1561 assert_eq!(root_svc.0, 1);
1562
1563 let middle_svc = leaf.get::<MiddleService>().unwrap();
1564 assert_eq!(middle_svc.0, 2);
1565
1566 let leaf_svc = leaf.get::<LeafService>().unwrap();
1567 assert_eq!(leaf_svc.0, 4);
1568
1569 // Middle2 should also access ancestor services
1570 assert!(middle2.contains::<RootService>());
1571 assert!(middle2.contains::<MiddleService>());
1572 assert!(!middle2.contains::<LeafService>()); // Leaf service not in parent
1573 }
1574}