ferrunix_core/registry.rs
1//! Holds all registered types that can be injected or constructed.
2#![allow(clippy::multiple_inherent_impl)]
3
4use std::any::TypeId;
5use std::marker::PhantomData;
6
7use crate::cycle_detection::{
8 DependencyValidator, FullValidationError, ValidationError,
9};
10use crate::dependency_builder::DepBuilder;
11use crate::object_builder::Object;
12use crate::types::{
13 Registerable, RegisterableSingleton, SingletonCtor, SingletonCtorDeps,
14};
15use crate::{
16 registration::RegistrationFunc, registration::DEFAULT_REGISTRY,
17 types::HashMap, types::Ref, types::RwLock,
18};
19
20/// Registry for all types that can be constructed or otherwise injected.
21pub struct Registry {
22 /// Internal hashtable of all registered objects.
23 objects: RwLock<HashMap<TypeId, Object>>,
24 /// Validation.
25 validator: DependencyValidator,
26}
27
28#[allow(clippy::multiple_inherent_impl)]
29impl Registry {
30 /// Create a new, empty, registry. This registry contains no pre-registered
31 /// types.
32 ///
33 /// Types that are auto-registered are also not included in this registry.
34 ///
35 /// To get access to the auto-registered types (types that are annotated by
36 /// the derive macro), the global registry [`Registry::global`] needs to
37 /// be used.
38 #[must_use]
39 pub fn empty() -> Self {
40 Self {
41 objects: RwLock::new(HashMap::new()),
42 validator: DependencyValidator::new(),
43 }
44 }
45
46 /// Register a new transient or singleton with dependencies.
47 #[cfg_attr(feature = "tracing", tracing::instrument)]
48 pub fn with_deps<T, Deps>(&self) -> Builder<'_, T, Deps>
49 where
50 Deps: DepBuilder<T>,
51 {
52 Builder {
53 registry: self,
54 _marker: PhantomData,
55 _marker1: PhantomData,
56 }
57 }
58
59 /// Check whether all registered types have the required dependencies.
60 ///
61 /// This is a potentially expensive call since it needs to go through the
62 /// entire dependency tree for each registered type.
63 ///
64 /// Nontheless, it's recommended to call this before using the [`Registry`].
65 ///
66 /// # Errors
67 /// Returns a [`ValidationError`] when the dependency graph is missing dependencies or
68 /// has cycles.
69 #[cfg_attr(feature = "tracing", tracing::instrument)]
70 pub fn validate_all(&self) -> Result<(), ValidationError> {
71 self.validator.validate_all()
72 }
73
74 /// Check whether all registered types have the required dependencies and returns a
75 /// detailed error about what's missing or where a cycle was detected.
76 ///
77 /// This is a potentially expensive call since it needs to go through the
78 /// entire dependency tree for each registered type.
79 ///
80 /// # Errors
81 /// Returns a [`FullValidationError`] when the dependency graph is missing dependencies or has
82 /// cycles.
83 #[cfg_attr(feature = "tracing", tracing::instrument)]
84 pub fn validate_all_full(&self) -> Result<(), FullValidationError> {
85 self.validator.validate_all_full()
86 }
87
88 /// Check whether the type `T` is registered in this registry, and all
89 /// dependencies of the type `T` are also registered.
90 ///
91 /// # Errors
92 /// Returns a [`ValidationError`] when the dependency graph is missing dependencies or has cycles.
93 #[cfg_attr(feature = "tracing", tracing::instrument)]
94 pub fn validate<T>(&self) -> Result<(), ValidationError>
95 where
96 T: Registerable,
97 {
98 self.validator.validate::<T>()
99 }
100
101 /// Return a string of the dependency graph visualized using graphviz's `dot` language.
102 ///
103 /// # Errors
104 /// Returns a [`ValidationError`] when the dependency graph is missing dependencies or has cycles.
105 #[cfg_attr(feature = "tracing", tracing::instrument)]
106 pub fn dotgraph(&self) -> Result<String, ValidationError> {
107 self.validator.dotgraph()
108 }
109
110 /// Access the global registry.
111 ///
112 /// This registry contains the types that are marked for auto-registration
113 /// via the derive macro.
114 #[cfg(all(not(feature = "tokio"), not(feature = "multithread")))]
115 #[cfg_attr(feature = "tracing", tracing::instrument)]
116 pub fn global() -> std::rc::Rc<Self> {
117 DEFAULT_REGISTRY.with(|val| {
118 let ret =
119 val.get_or_init(|| std::rc::Rc::new(Self::autoregistered()));
120 std::rc::Rc::clone(ret)
121 })
122 }
123}
124
125#[cfg(all(feature = "multithread", not(feature = "tokio")))]
126impl Registry {
127 /// Access the global registry.
128 ///
129 /// This registry contains the types that are marked for auto-registration
130 /// via the derive macro.
131 #[cfg_attr(feature = "tracing", tracing::instrument)]
132 pub fn global() -> &'static Self {
133 DEFAULT_REGISTRY.get_or_init(Self::autoregistered)
134 }
135}
136
137#[cfg(not(feature = "tokio"))]
138impl Registry {
139 /// Register a new transient object, without dependencies.
140 ///
141 /// To register a type with dependencies, use the builder returned from
142 /// [`Registry::with_deps`].
143 ///
144 /// # Parameters
145 /// * `ctor`: A constructor function returning the newly constructed `T`.
146 /// This constructor will be called for every `T` that is requested.
147 ///
148 /// # Panics
149 /// When the type has been registered already.
150 #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
151 pub fn transient<T>(&self, ctor: fn() -> T)
152 where
153 T: Registerable,
154 {
155 use crate::object_builder::TransientBuilderImplNoDeps;
156
157 #[cfg(feature = "tracing")]
158 tracing::info!(
159 "registering transient ({})",
160 std::any::type_name::<T>()
161 );
162
163 let transient =
164 Object::Transient(Box::new(TransientBuilderImplNoDeps::new(ctor)));
165
166 self.insert_or_panic::<T>(transient);
167 self.validator.add_transient_no_deps::<T>();
168 }
169
170 /// Register a new singleton object, without dependencies.
171 ///
172 /// To register a type with dependencies, use the builder returned from
173 /// [`Registry::with_deps`].
174 ///
175 /// # Parameters
176 /// * `ctor`: A constructor function returning the newly constructed `T`.
177 /// This constructor will be called once, lazily, when the first
178 /// instance of `T` is requested.
179 ///
180 /// # Panics
181 /// When the type has been registered already.
182 #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
183 pub fn singleton<T, F>(&self, ctor: F)
184 where
185 T: RegisterableSingleton,
186 F: SingletonCtor<T>,
187 {
188 use crate::object_builder::SingletonGetterNoDeps;
189
190 #[cfg(feature = "tracing")]
191 tracing::info!(
192 "registering singleton ({})",
193 std::any::type_name::<T>()
194 );
195
196 let singleton =
197 Object::Singleton(Box::new(SingletonGetterNoDeps::new(ctor)));
198
199 self.insert_or_panic::<T>(singleton);
200 self.validator.add_singleton_no_deps::<T>();
201 }
202
203 /// Retrieves a newly constructed `T` from this registry.
204 ///
205 /// Returns `None` if `T` wasn't registered or failed to construct.
206 #[must_use]
207 #[cfg_attr(feature = "tracing", tracing::instrument)]
208 pub fn get_transient<T>(&self) -> Option<T>
209 where
210 T: Registerable,
211 {
212 let lock = self.objects.read();
213 if let Some(Object::Transient(transient)) = lock.get(&TypeId::of::<T>())
214 {
215 let resolved = transient.make_transient(self)?;
216 drop(lock);
217 if let Ok(obj) = resolved.downcast::<T>() {
218 return Some(*obj);
219 }
220 }
221
222 None
223 }
224
225 /// Retrieves the singleton `T` from this registry.
226 ///
227 /// Returns `None` if `T` wasn't registered or failed to construct. The
228 /// singleton is a ref-counted pointer object (either `Arc` or `Rc`).
229 #[must_use]
230 #[cfg_attr(feature = "tracing", tracing::instrument)]
231 pub fn get_singleton<T>(&self) -> Option<Ref<T>>
232 where
233 T: RegisterableSingleton,
234 {
235 let lock = self.objects.read();
236 if let Some(Object::Singleton(singleton)) = lock.get(&TypeId::of::<T>())
237 {
238 let resolved = singleton.get_singleton(self)?;
239 drop(lock);
240 if let Ok(obj) = resolved.downcast::<T>() {
241 return Some(obj);
242 }
243 }
244
245 None
246 }
247
248 /// Reset the global registry, removing all previously registered types, and
249 /// re-running the auto-registration routines.
250 ///
251 /// # Safety
252 /// Ensure that no other thread is currently using [`Registry::global()`].
253 #[allow(unsafe_code)]
254 #[cfg_attr(feature = "tracing", tracing::instrument)]
255 pub unsafe fn reset_global() {
256 let registry = Self::global();
257 {
258 let mut lock = registry.objects.write();
259 lock.clear();
260 }
261
262 for register in inventory::iter::<RegistrationFunc> {
263 #[cfg(not(feature = "multithread"))]
264 (register.0)(®istry);
265
266 #[cfg(feature = "multithread")]
267 (register.0)(registry);
268 }
269 }
270
271 /// Create an empty registry, and add all autoregistered types into it.
272 ///
273 /// This is the constructor for the global registry that can be acquired
274 /// with [`Registry::global`].
275 #[must_use]
276 #[cfg_attr(feature = "tracing", tracing::instrument)]
277 pub fn autoregistered() -> Self {
278 let registry = Self::empty();
279 for register in inventory::iter::<RegistrationFunc> {
280 (register.0)(®istry);
281 }
282
283 registry
284 }
285
286 /// Inserts a new object into the objecs hashtable.
287 ///
288 /// This acquires an exclusive lock on `self.objects`.
289 ///
290 /// # Panics
291 /// If the key already exists (=> the type was previously registered).
292 #[inline]
293 fn insert_or_panic<T: 'static>(&self, value: Object) {
294 let mut lock = self.objects.write();
295 let entry = lock.entry(TypeId::of::<T>());
296 match entry {
297 #[allow(clippy::panic)]
298 hashbrown::hash_map::Entry::Occupied(_) => panic!(
299 "Type '{}' ({:?}) is already registered",
300 std::any::type_name::<T>(),
301 TypeId::of::<T>()
302 ),
303 hashbrown::hash_map::Entry::Vacant(view) => {
304 view.insert(value);
305 }
306 }
307 }
308}
309
310#[cfg(feature = "tokio")]
311impl Registry {
312 /// Create an empty registry, and add all autoregistered types into it.
313 ///
314 /// This is the constructor for the global registry that can be acquired
315 /// with [`Registry::global`].
316 ///
317 /// # Panics
318 /// If any of the constructors panic.
319 #[must_use]
320 #[cfg_attr(feature = "tracing", tracing::instrument)]
321 pub async fn autoregistered() -> Self {
322 use std::sync::Arc;
323
324 let registry = Arc::new(Self::empty());
325
326 let mut set = tokio::task::JoinSet::new();
327 for register in inventory::iter::<RegistrationFunc> {
328 let registry = Arc::clone(®istry);
329 set.spawn(async move {
330 let inner_registry = registry;
331 (register.0)(&inner_registry).await;
332 });
333 }
334
335 #[allow(clippy::panic)]
336 while let Some(res) = set.join_next().await {
337 match res {
338 Ok(_) => continue,
339 Err(err) if err.is_panic() => {
340 std::panic::resume_unwind(err.into_panic())
341 }
342 Err(err) => panic!("{err}"),
343 }
344 }
345
346 assert_eq!(
347 Arc::strong_count(®istry), 1,
348 "all of the tasks in the `JoinSet` should've joined, dropping their \
349 Arc's. some task is still holding an Arc");
350 Arc::try_unwrap(registry).expect("all tasks above are joined")
351 }
352
353 /// Register a new singleton object, without dependencies.
354 ///
355 /// To register a type with dependencies, use the builder returned from
356 /// [`Registry::with_deps`].
357 ///
358 /// # Parameters
359 /// * `ctor`: A constructor function returning the newly constructed `T`.
360 /// This constructor will be called once, lazily, when the first
361 /// instance of `T` is requested.
362 ///
363 /// # Panics
364 /// When the type has been registered already.
365 #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
366 pub async fn singleton<T, F>(&self, ctor: F)
367 where
368 T: RegisterableSingleton,
369 F: SingletonCtor<T>,
370 {
371 use crate::object_builder::AsyncSingletonNoDeps;
372
373 #[cfg(feature = "tracing")]
374 tracing::info!(
375 "registering singleton ({})",
376 std::any::type_name::<T>()
377 );
378
379 let singleton =
380 Object::AsyncSingleton(Box::new(AsyncSingletonNoDeps::new(ctor)));
381
382 self.insert_or_panic::<T>(singleton).await;
383 self.validator.add_singleton_no_deps::<T>();
384 }
385
386 /// Register a new transient object, without dependencies.
387 ///
388 /// To register a type with dependencies, use the builder returned from
389 /// [`Registry::with_deps`].
390 ///
391 /// # Parameters
392 /// * `ctor`: A constructor function returning the newly constructed `T`.
393 /// This constructor will be called for every `T` that is requested.
394 ///
395 /// # Panics
396 /// When the type has been registered already.
397 #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
398 pub async fn transient<T>(
399 &self,
400 ctor: fn() -> std::pin::Pin<
401 Box<dyn std::future::Future<Output = T> + Send>,
402 >,
403 ) where
404 T: Registerable,
405 {
406 use crate::object_builder::AsyncTransientBuilderImplNoDeps;
407
408 #[cfg(feature = "tracing")]
409 tracing::info!(
410 "registering transient ({})",
411 std::any::type_name::<T>()
412 );
413
414 let transient = Object::AsyncTransient(Box::new(
415 AsyncTransientBuilderImplNoDeps::new(ctor),
416 ));
417
418 self.insert_or_panic::<T>(transient).await;
419 self.validator.add_transient_no_deps::<T>();
420 }
421
422 /// Retrieves a newly constructed `T` from this registry.
423 ///
424 /// Returns `None` if `T` wasn't registered or failed to construct.
425 #[must_use]
426 #[cfg_attr(feature = "tracing", tracing::instrument)]
427 pub async fn get_transient<T>(&self) -> Option<T>
428 where
429 T: Registerable,
430 {
431 let lock = self.objects.read().await;
432 if let Some(Object::AsyncTransient(ctor)) = lock.get(&TypeId::of::<T>())
433 {
434 let boxed = ctor.make_transient(self).await?;
435 drop(lock);
436 if let Ok(obj) = boxed.downcast::<T>() {
437 return Some(*obj);
438 }
439 }
440
441 None
442 }
443
444 /// Retrieves the singleton `T` from this registry.
445 ///
446 /// Returns `None` if `T` wasn't registered or failed to construct. The
447 /// singleton is a ref-counted pointer object (either `Arc` or `Rc`).
448 #[must_use]
449 #[cfg_attr(feature = "tracing", tracing::instrument)]
450 pub async fn get_singleton<T>(&self) -> Option<Ref<T>>
451 where
452 T: RegisterableSingleton,
453 {
454 let lock = self.objects.read().await;
455 if let Some(Object::AsyncSingleton(singleton)) =
456 lock.get(&TypeId::of::<T>())
457 {
458 let resolved = singleton.get_singleton(self).await?;
459 drop(lock);
460 if let Ok(obj) = resolved.downcast::<T>() {
461 return Some(obj);
462 }
463 }
464
465 None
466 }
467
468 /// Access the global registry.
469 ///
470 /// This registry contains the types that are marked for auto-registration
471 /// via the derive macro.
472 #[cfg_attr(feature = "tracing", tracing::instrument)]
473 pub async fn global() -> &'static Self {
474 DEFAULT_REGISTRY.get_or_init(Self::autoregistered).await
475 }
476
477 /// Reset the global registry, removing all previously registered types, and
478 /// re-running the auto-registration routines.
479 ///
480 /// # Safety
481 /// Ensure that no other thread is currently using [`Registry::global()`].
482 #[allow(unsafe_code)]
483 pub async unsafe fn reset_global() {
484 // Purposefully not annotated with `tracing::instrument` because it mangles the order of
485 // `async` and `unsafe`, resulting in a compiler error.
486 let registry = Self::global().await;
487 {
488 let mut lock = registry.objects.write().await;
489 lock.clear();
490 }
491
492 for register in inventory::iter::<RegistrationFunc> {
493 (register.0)(registry).await;
494 }
495 }
496
497 /// Inserts a new object into the objecs hashtable.
498 ///
499 /// This acquires an exclusive lock on `self.objects`.
500 ///
501 /// # Panics
502 /// If the key already exists (=> the type was previously registered).
503 #[inline]
504 async fn insert_or_panic<T: 'static>(&self, value: Object) {
505 let mut lock = self.objects.write().await;
506 let entry = lock.entry(TypeId::of::<T>());
507 match entry {
508 #[allow(clippy::panic)]
509 hashbrown::hash_map::Entry::Occupied(_) => panic!(
510 "Type '{}' ({:?}) is already registered",
511 std::any::type_name::<T>(),
512 TypeId::of::<T>()
513 ),
514 hashbrown::hash_map::Entry::Vacant(view) => {
515 view.insert(value);
516 }
517 }
518 }
519}
520
521impl std::fmt::Debug for Registry {
522 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523 fmt.debug_struct("Registry").finish()
524 }
525}
526
527/// A builder for objects with dependencies. This can be created by using
528/// [`Registry::with_deps`].
529#[allow(clippy::single_char_lifetime_names)]
530pub struct Builder<'reg, T, Deps> {
531 /// Reference to parent registry.
532 registry: &'reg Registry,
533 /// Marker for `T`.
534 _marker: PhantomData<T>,
535 /// Marker for `Deps`.
536 _marker1: PhantomData<Deps>,
537}
538
539impl<
540 T,
541 #[cfg(not(feature = "tokio"))] Deps: DepBuilder<T> + 'static,
542 #[cfg(feature = "tokio")] Deps: DepBuilder<T> + Sync + 'static,
543 > Builder<'_, T, Deps>
544where
545 T: Registerable,
546{
547 /// Register a new transient object, with dependencies specified in
548 /// `.with_deps`.
549 ///
550 /// The `ctor` parameter is a constructor function returning the newly
551 /// constructed `T`. The constructor accepts a single argument `Deps` (a
552 /// tuple implementing [`crate::dependency_builder::DepBuilder`]). It's
553 /// best to destructure the tuple to accept each dependency separately.
554 /// This constructor will be called for every `T` that is requested.
555 ///
556 /// # Example
557 /// ```rust,no_run
558 /// # use ferrunix_core::{Registry, Singleton, Transient};
559 /// # let registry = Registry::empty();
560 /// # struct Template {
561 /// # template: &'static str,
562 /// # }
563 /// registry
564 /// .with_deps::<_, (Transient<u8>, Singleton<Template>)>()
565 /// .transient(|(num, template)| {
566 /// // access `num` and `template` here.
567 /// u16::from(*num)
568 /// });
569 /// ```
570 ///
571 /// For single dependencies, the destructured tuple needs to end with a
572 /// comma: `(dep,)`.
573 ///
574 /// # Panics
575 /// When the type has been registered already.
576 #[cfg(not(feature = "tokio"))]
577 #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
578 pub fn transient(&self, ctor: fn(Deps) -> T) {
579 use crate::object_builder::TransientBuilderImplWithDeps;
580
581 #[cfg(feature = "tracing")]
582 tracing::info!(
583 "registering transient (with dependencies) ({})",
584 std::any::type_name::<T>()
585 );
586
587 let transient = Object::Transient(Box::new(
588 TransientBuilderImplWithDeps::new(ctor),
589 ));
590
591 self.registry.insert_or_panic::<T>(transient);
592 self.registry.validator.add_transient_deps::<T, Deps>();
593 }
594
595 /// Register a new transient object, with dependencies specified in
596 /// `.with_deps`.
597 ///
598 /// The `ctor` parameter is a constructor function returning the newly
599 /// constructed `T`. The constructor accepts a single argument `Deps` (a
600 /// tuple implementing [`crate::dependency_builder::DepBuilder`]). It's
601 /// best to destructure the tuple to accept each dependency separately.
602 /// This constructor will be called for every `T` that is requested.
603 ///
604 /// The `ctor` must return a boxed `dyn Future`.
605 ///
606 /// # Panics
607 /// When the type has been registered already.
608 #[cfg(feature = "tokio")]
609 #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
610 pub async fn transient(
611 &self,
612 ctor: fn(
613 Deps,
614 ) -> std::pin::Pin<
615 Box<dyn std::future::Future<Output = T> + Send>,
616 >,
617 ) {
618 use crate::object_builder::AsyncTransientBuilderImplWithDeps;
619
620 #[cfg(feature = "tracing")]
621 tracing::info!(
622 "registering transient (with dependencies) ({})",
623 std::any::type_name::<T>()
624 );
625
626 let transient = Object::AsyncTransient(Box::new(
627 AsyncTransientBuilderImplWithDeps::new(ctor),
628 ));
629
630 self.registry.insert_or_panic::<T>(transient).await;
631 self.registry.validator.add_transient_deps::<T, Deps>();
632 }
633}
634
635impl<
636 T,
637 #[cfg(not(feature = "tokio"))] Deps: DepBuilder<T> + 'static,
638 #[cfg(feature = "tokio")] Deps: DepBuilder<T> + Sync + 'static,
639 > Builder<'_, T, Deps>
640where
641 T: RegisterableSingleton,
642{
643 /// Register a new singleton object, with dependencies specified in
644 /// `.with_deps`.
645 ///
646 /// The `ctor` parameter is a constructor function returning the newly
647 /// constructed `T`. The constructor accepts a single argument `Deps` (a
648 /// tuple implementing [`crate::dependency_builder::DepBuilder`]). It's
649 /// best to destructure the tuple to accept each dependency separately.
650 /// This constructor will be called once, lazily, when the first
651 /// instance of `T` is requested.
652 ///
653 /// # Example
654 /// ```rust,no_run
655 /// # use ferrunix_core::{Registry, Singleton, Transient};
656 /// # let registry = Registry::empty();
657 /// # struct Template {
658 /// # template: &'static str,
659 /// # }
660 /// registry
661 /// .with_deps::<_, (Transient<u8>, Singleton<Template>)>()
662 /// .transient(|(num, template)| {
663 /// // access `num` and `template` here.
664 /// u16::from(*num)
665 /// });
666 /// ```
667 ///
668 /// For single dependencies, the destructured tuple needs to end with a
669 /// comma: `(dep,)`.
670 #[cfg(not(feature = "tokio"))]
671 #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
672 pub fn singleton<F>(&self, ctor: F)
673 where
674 F: SingletonCtorDeps<T, Deps>,
675 {
676 use crate::object_builder::SingletonGetterWithDeps;
677
678 #[cfg(feature = "tracing")]
679 tracing::info!(
680 "registering singleton (with dependencies) ({})",
681 std::any::type_name::<T>()
682 );
683
684 let singleton =
685 Object::Singleton(Box::new(SingletonGetterWithDeps::new(ctor)));
686
687 self.registry.insert_or_panic::<T>(singleton);
688 self.registry.validator.add_singleton_deps::<T, Deps>();
689 }
690
691 /// Register a new singleton object, with dependencies specified in
692 /// `.with_deps`.
693 ///
694 /// The `ctor` parameter is a constructor function returning the newly
695 /// constructed `T`. The constructor accepts a single argument `Deps` (a
696 /// tuple implementing [`crate::dependency_builder::DepBuilder`]). It's
697 /// best to destructure the tuple to accept each dependency separately.
698 /// This constructor will be called once, lazily, when the first
699 /// instance of `T` is requested.
700 ///
701 /// The `ctor` must return a boxed `dyn Future`.
702 #[cfg(feature = "tokio")]
703 #[cfg_attr(feature = "tracing", tracing::instrument(skip(ctor)))]
704 pub async fn singleton<F>(&self, ctor: F)
705 where
706 F: SingletonCtorDeps<T, Deps>,
707 {
708 use crate::object_builder::AsyncSingletonWithDeps;
709
710 #[cfg(feature = "tracing")]
711 tracing::info!(
712 "registering singleton (with dependencies) ({})",
713 std::any::type_name::<T>()
714 );
715
716 let singleton =
717 Object::AsyncSingleton(Box::new(AsyncSingletonWithDeps::new(ctor)));
718
719 self.registry.insert_or_panic::<T>(singleton).await;
720 self.registry.validator.add_singleton_deps::<T, Deps>();
721 }
722}
723
724impl<T, Dep> std::fmt::Debug for Builder<'_, T, Dep> {
725 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
726 fmt.debug_struct("Builder").finish()
727 }
728}