icacher/
lib.rs

1//! # ICacher
2//! Running the same function (that return the same value) over and over again
3//! can be inefficient. This lightweight, dependency-free crate attempts to
4//! solve this problem by caching each return value. It will only, unless 
5//! explicitly called to run multiple times or if the value isn't cached, be called once.
6
7use std::{collections::HashMap, hash::Hash};
8
9/// This trait provides core functionality
10/// of a function cacher:
11/// * `new()`
12/// * `with_arg()`
13pub trait FnCacher<IFunc, IType, IReturn>
14where
15    IFunc: Fn(IType) -> IReturn,
16    IType: Clone + Hash + Eq,
17    IReturn: Clone,
18{
19    /// Creates a new instance of the cacher struct.
20    /// This method takes in a [`Fn`] closure which is stored
21    /// in the struct instance.
22    fn new(func: IFunc) -> Self;
23
24    /// Returns the inner value.
25    ///
26    /// If the value is not found in a [`HashMap`], it will run the
27    /// function, with the `arg` argument passed in, insert the value
28    /// in the HashMap, and return the new value
29    fn with_arg(&mut self, arg: IFunc) -> IReturn;
30}
31
32/// This trait is deprecated; however, the trait [`FnCacher`] remains
33/// as to allow basic, but minimalistic, guidance.
34#[deprecated = "You should manually implement instead of the now deprecated traits."]
35pub trait FnCacherExt<IFunc, IType, IReturn>: FnCacher<IFunc, IType, IReturn>
36where
37    IFunc: Fn(IType) -> IReturn,
38    IType: Clone + Hash + Eq,
39    IReturn: Clone,
40{
41    /// Clears the HashMap.
42    fn reset(&mut self);
43
44    /// Modifies the closure of the Cacher.
45    ///
46    /// Note that calling this function will clear the HashMap.
47    /// If you want to achieve the same but without resetting it,
48    /// use the `to_unchanged()` method.
49    fn to(&mut self, f: IType);
50
51    /// Same as `to()` except that it does not change the value
52    /// at all.
53    fn to_unchanged(&mut self, f: IType);
54}
55
56/// This trait, as well as [`FnCacherExt`], is deprecated as extension traits
57/// did not provide any benefits; methods that were in the
58/// trait are now provided directly in the [`ICacher`] type.
59///
60/// The reason they are marked as deprecated and not removed,
61/// is that to minimise the amount of breaking changes of other
62/// codebases.
63#[deprecated = "You should manually implement instead of the now deprecated traits."]
64pub trait ICacherExt<IFunc, IType, IReturn>: __private::Sealed
65where
66    IFunc: Fn(IType) -> IReturn,
67    IType: Clone + Hash + Eq,
68    IReturn: Clone,
69{
70    /// Clears the HashMap
71    fn reset(&mut self);
72
73    /// Modifies the closure of the Cacher.
74    ///
75    /// Note that calling this function will clear the HashMap.
76    /// If you want to achieve the same but without resetting it,
77    /// use the `to_unchanged()` method.
78    fn to(&mut self, func: IFunc);
79
80    /// Same as `to()` except that it does not change the value
81    /// at all.
82    fn to_unchanged(&mut self, func: IFunc);
83}
84
85/// The built-in, default, generic type for caching functions and
86/// storing its value in a [`HashMap`].
87#[derive(Debug, Clone)]
88pub struct ICacher<IFunc, IType, IReturn>
89where
90    IFunc: Fn(IType) -> IReturn,
91    IType: Clone + Hash + Eq,
92    IReturn: Clone,
93{
94    func: IFunc,
95    values: HashMap<IType, IReturn>,
96}
97
98impl<IFunc, IType, IReturn> ICacher<IFunc, IType, IReturn>
99where
100    IFunc: Fn(IType) -> IReturn,
101    IType: Clone + Hash + Eq,
102    IReturn: Clone,
103{
104    /// Creates a new [`ICacher`] instance.
105    /// This takes in a closure which is expected to
106    /// have at least one argument.
107    ///
108    /// # Notes
109    /// * Use the `()` type if you do not want to return
110    ///  anything.
111    /// * If you need to have multiple parameters, enclose
112    /// them in a tuple.
113    /// * You can set a capacity of the HashMap: this means that
114    /// the HashMap will be able to hold a certain amount of elements
115    /// without reallocating. This is memory efficient as reallocating
116    /// too much can slow the program and use too much memory.  
117    ///
118    /// # Example
119    /// Caches a closure with 2 arguments, enclosed in a
120    /// tuple to simulate multiple arguments, but it is
121    /// actually one.
122    /// ```
123    /// use icacher::ICacher;
124    /// let mut adder = ICacher::new(|(a, b): (i32, i32)| a + b, Some(1));
125    /// // Explicit type for `a` and `b` are needed,
126    /// // but can be inferred from usage.
127    #[inline]
128    pub fn new(func: IFunc, capacity: Option<usize>) -> Self {
129        ICacher {
130            func,
131            values: HashMap::with_capacity(capacity.unwrap_or_default()),
132        }
133    }
134
135    /// Runs the closure given. If there is a value found
136    /// to be in the HashMap, it will return the cached value.
137    /// Otherwise, it will return a new value and cache that value
138    /// by inserting it into the HashMap.
139    ///
140    /// # Example
141    /// ```
142    /// use icacher::ICacher;
143    ///
144    /// let mut adder = ICacher::new(|(a, b)| a + b, 1);
145    /// let value = adder.with_arg((20, 30));
146    ///
147    /// assert_eq!(value, 50);
148    /// ```
149    #[inline]
150    pub fn with_arg(&mut self, arg: IType) -> IReturn {
151        if self.values.contains_key(&arg) {
152            return self.values[&arg].clone();
153        }
154
155        let value = (self.func)(arg.clone());
156        self.values.insert(arg, value.clone());
157        value
158    }
159
160    /// Clears the HashMap
161    #[inline]
162    pub fn reset(&mut self) {
163        self.values.clear();
164    }
165
166    /// Modifies the closure of the Cacher.
167    ///
168    /// Note that calling this function will clear the HashMap.
169    /// If you want to achieve the same but without resetting it,
170    /// use the `to_unchanged()` method.
171    #[inline]
172    pub fn to(&mut self, func: IFunc) {
173        self.to_unchanged(func);
174        self.values.clear();
175    }
176
177    /// Same as `to()` except that it does not change the value
178    /// at all.
179    #[inline]
180    pub fn to_unchanged(&mut self, func: IFunc) {
181        self.func = func;
182    }
183
184    /// Checks if a function's result is cached.
185    #[inline]
186    pub fn is_cached(&self, arg: &IType) -> bool {
187        self.values.contains_key(&arg)
188    }
189
190    /// Removes a function's result and returns the result if it were found.
191    ///
192    /// Returns [`None`] if there weren't any found.
193    ///
194    /// # Example
195    /// ```
196    /// use icacher::ICacher;
197    ///
198    /// let mut multiplier = ICacher::new(|(a, b)| a * b, Some(1));
199    ///
200    /// let _ = multiplier.with_arg((5, 5));
201    ///
202    /// assert!(multiplier.is_cached(&(5, 5)));
203    ///
204    /// let _ = multiplier.remove_cache((5, 5));
205    ///
206    /// assert!(!multiplier.is_cached(&(5, 5)));
207    /// ```
208    #[inline]
209    pub fn remove_cache(&mut self, arg: IType) -> Option<IReturn> {
210        match self.values.remove(&arg) {
211            Some(val) => Some(val),
212            None => None,
213        }
214    }
215
216    /// Same as `ICacher::with_arg`, but it
217    /// does not return.
218    #[inline]
219    pub fn void(&mut self, arg: IType) {
220        self.with_arg(arg);
221    }
222
223    /// Caches the result only if the condition is true.
224    ///
225    /// # Notes
226    /// * If the value is already cached, it will return false
227    /// * The return type signals if the value has been cached or not.
228    ///
229    /// # Example
230    /// ```
231    /// use icacher::ICacher;
232    /// let mut adder = ICacher::new(|(a, b): (i32, i32)| a + b, 1);
233    ///
234    /// let a = 10;
235    /// let b = 10;
236    /// let c = 5;
237    ///
238    /// adder.void((a, b));
239    ///
240    /// let value = adder.cache_if(|| {a == b}, (a, b)); // this will not cache since it is already cached.
241    ///
242    /// let is_cached = adder.is_cached(&(a, b));
243    ///
244    /// let result = adder.cache_if(|| {is_cached}, (b, c));
245    ///
246    /// assert!(!value);
247    /// assert!(result);
248    /// ```
249    #[inline]
250    pub fn cache_if<Func: Fn() -> bool>(&mut self, func: Func, arg: IType) -> bool {
251        if self.is_cached(&arg) || !func() {
252            return false;
253        }
254
255        self.void(arg);
256        return true;
257    }
258}
259
260mod __private {
261    pub trait Sealed {}
262
263    impl<A, B, C> Sealed for super::ICacher<A, B, C>
264    where
265        A: Fn(B) -> C,
266        B: Clone + super::Hash + Eq,
267        C: Clone,
268    {
269    }
270}