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}