imperat_common/
dependencies.rs

1use std::{
2    any::{Any, TypeId},
3    collections::HashMap,
4    ops::Deref,
5    sync::Arc,
6};
7use variadics_please::all_tuples;
8
9/// Nearly 1-to-1 with this blog:
10/// <https://nickbryan.co.uk/software/using-a-type-map-for-dependency-injection-in-rust/>
11/// A `TypeMap` uniquely stores an arbitrary value by its type. No types
12/// can store more than one value.
13#[derive(Default, Debug)]
14pub struct TypeMap {
15    bindings: HashMap<TypeId, Box<dyn Any>>,
16}
17
18impl TypeMap {
19    /// Creates a new, empty type map.
20    pub fn new() -> Self {
21        TypeMap::default()
22    }
23
24    /// Binds the given value to its type in the type map. If an
25    /// existing value for this type exists, it's returned. An existing value
26    /// with an incorrect type is returned as none.
27    pub fn bind<T: Any>(&mut self, val: T) -> Option<Box<T>> {
28        self.bindings
29            .insert(val.type_id(), Box::new(val))
30            .and_then(|v| v.downcast().ok())
31    }
32
33    /// Returns the value in this type map for this unique type.
34    pub fn get<T: Any>(&self) -> Option<&T> {
35        self.bindings
36            .get(&TypeId::of::<T>())
37            .and_then(|boxed| boxed.downcast_ref())
38    }
39}
40
41/// A type which can be retrieved from a type map. Its type signature
42/// uniquely stores the type in the map.
43pub trait FromTypeMap: Any + Sized {
44    fn retrieve_from_map(tm: &TypeMap) -> Option<Self>;
45}
46
47// Fans out an implementation for 0 to 16-tuple of generics of FromTypeMap. Allows
48// the crate to treat a tuple of arguments as individiual arguments to look up
49// in a type map. Without this, we'd look up all unique argument as a tuple on
50// a function call when resolving dependencies.
51//
52// In effect, this is a big part of where the magic happens.
53macro_rules! impl_fromtypemap_tuples {
54    ($($param: ident),*) => {
55        #[allow(
56            non_snake_case,
57            reason = "Certain variable names are provided by the caller, not by us."
58        )]
59        #[allow(
60            unused_variables,
61            reason = "Zero-length tuples won't use some of the parameters."
62        )]
63        #[expect(
64            clippy::allow_attributes,
65            reason = "This is in a macro, and as such, the below lints may not always apply."
66        )]
67        impl<$($param: FromTypeMap,)*> FromTypeMap for ($($param,)*) {
68            fn retrieve_from_map(tm: &TypeMap) -> Option<Self> {
69                (
70                    Some(($(
71                        $param::retrieve_from_map(tm)?,
72                    )*))
73                )
74            }
75        }
76    }
77}
78
79all_tuples!(impl_fromtypemap_tuples, 0, 16, F);
80
81/// A dependency which can be automatically resolved at runtime
82/// by its unique type.
83pub struct Dep<T: ?Sized>(Arc<T>);
84
85impl<T> Dep<T> {
86    /// Create a new dependency for injection.
87    pub fn new(val: T) -> Dep<T> {
88        Dep(Arc::new(val))
89    }
90
91    /// Yields the inner dependency, destroying the outer wrapper.
92    #[must_use]
93    pub fn inner(self) -> Arc<T> {
94        self.0
95    }
96}
97
98impl<T: ?Sized> Clone for Dep<T> {
99    fn clone(&self) -> Self {
100        Dep(self.0.clone())
101    }
102}
103
104impl<T: ?Sized> Deref for Dep<T> {
105    type Target = Arc<T>;
106
107    fn deref(&self) -> &Arc<T> {
108        &self.0
109    }
110}
111
112impl<T: ?Sized + 'static> FromTypeMap for Dep<T> {
113    fn retrieve_from_map(tm: &TypeMap) -> Option<Self> {
114        tm.get::<Self>().cloned()
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    struct Database;
123    #[derive(Debug)]
124    struct Config(i32, u32);
125
126    // retrieval should get back what it puts in
127    #[test]
128    fn test_retrieval() {
129        let mut tm = TypeMap::new();
130
131        tm.bind(Dep::new(Database));
132        tm.bind(Dep::new(Config(2, 3)));
133
134        tm.get::<Dep<Database>>().unwrap();
135        let cfg = tm.get::<Dep<Config>>().unwrap();
136
137        // since we're in this module, we have to navigate the
138        // private internals of Dep
139        assert_eq!(cfg.0.0, 2);
140        assert_eq!(cfg.0.1, 3);
141    }
142
143    // unset values should be absent
144    #[test]
145    fn test_missing() {
146        let tm = TypeMap::new();
147        assert!(tm.get::<Dep<i32>>().is_none());
148    }
149}