repr_rs/
lib.rs

1// TODO: when this is stable, we can make `read` and `write` const-fns.
2// #![feature(const_mut_refs)]
3
4pub mod repr;
5pub mod cache;
6
7#[cfg(feature = "eager")]
8pub use cache::eager::EagerCacheLookup;
9pub use cache::CacheableRepr;
10pub use repr::Repr;
11
12#[cfg(test)]
13mod tests {
14	use std::borrow::Cow;
15	use crate::repr::Repr;
16	use crate::CacheableRepr;
17	use std::cell::RefCell;
18	use std::collections::HashMap;
19	use std::rc::Rc;
20	use std::sync::{Arc};
21	use tokio::sync::RwLock;
22
23	#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
24	struct MinMax {
25		min: i32,
26		max: i32,
27	}
28
29	#[test]
30	fn reading() {
31		let repr = Repr::new(
32			MinMax { min: 1, max: 5 },
33			|mm| mm.min < mm.max,
34		);
35		assert_eq!(repr.read().min, 1);
36		assert_eq!(repr.read().max, 5);
37	}
38
39	#[test]
40	fn reading_as_ref() {
41		let repr = Repr::new(
42			MinMax { min: 1, max: 5 },
43			|mm| mm.min < mm.max,
44		);
45		fn get_min<M: AsRef<MinMax>>(x: M) -> i32 { x.as_ref().min }
46		assert_eq!(get_min(&repr), 1);
47		assert_eq!(repr.read().max, 5);
48	}
49
50	#[test]
51	fn allowed_mutation() {
52		let mut repr = Repr::new(
53			MinMax { min: 1, max: 5 },
54			|mm| mm.min < mm.max,
55		);
56		let a = repr.read().min;
57		repr.write().min = 4;
58		assert_eq!(4, repr.read().min);
59		assert_eq!(1, a);
60	}
61
62	#[test]
63	#[should_panic]
64	fn should_propagate_panic() {
65		let mut repr = Repr::new(
66			MinMax { min: 1, max: 5 },
67			|mm| {
68				if mm.max <= 5 {
69					mm.min < mm.max
70				} else {
71					panic!("random panic")
72				}
73			},
74		);
75		let a = repr.read().min;
76		{
77			repr.write().min = 4;
78			assert_eq!(4, repr.read().min);
79			assert_eq!(1, a);
80		}
81		repr.write().max = 10;
82	}
83
84	#[test]
85	#[should_panic]
86	fn banned_mutation() {
87		let mut repr = Repr::new(
88			MinMax { min: 1, max: 5 },
89			|mm| mm.min < mm.max,
90		);
91		repr.write().min = 6;
92	}
93
94	#[test]
95	#[should_panic]
96	fn should_try_to_detect_non_deterministic_invariants() {
97		let value = Rc::new(RefCell::new(true));
98		let mut repr = Repr::new(
99			value.clone(),
100			|_| {
101				let res = *value.borrow();
102				value.replace(false);
103				res
104			},
105		);
106		repr.write();
107	}
108
109	#[test]
110	#[should_panic]
111	fn banned_mutation_with_msg() {
112		let mut repr = Repr::with_msg(
113			MinMax { min: 1, max: 5 },
114			|mm| mm.min < mm.max,
115			"min must always be less than max!",
116		);
117		repr.write().min = 6;
118	}
119	
120	#[test]
121	fn should_work_with_borrowed_data() {
122		#[derive(Debug)]
123		struct Person<'a> {
124			name: Cow<'a, str>,
125			age: u8,
126		}
127		impl Person<'_> {
128			fn is_valid(&self) -> bool {
129				!self.name.is_empty() && self.age < 200
130			}
131		}
132		let bob = String::from("Bob");
133		let mut repr = Repr::with_msg(
134			Person {
135				name: bob.as_str().into(),
136				age: 25,
137			},
138			Person::is_valid,
139			"People must have a name and cannot be older than 200",
140		);
141		{
142			let person = repr.read();
143			assert_eq!("Bob", person.name);
144			assert_eq!(25, person.age);
145		}
146		{
147			let mut person = repr.write();
148			person.name = "Alice".into();
149			person.age = 30;
150		}
151		{
152			let person = repr.read();
153			assert_eq!("Alice", person.name);
154			assert_eq!(30, person.age);
155		}
156	}
157	#[test]
158	#[should_panic]
159	fn should_work_with_borrowed_data_doing_a_bad_mutation() {
160		#[derive(Debug)]
161		struct Person<'a> {
162			name: Cow<'a, str>,
163			age: u8,
164		}
165		impl Person<'_> {
166			fn is_valid(&self) -> bool {
167				!self.name.is_empty() && self.age < 200
168			}
169		}
170		let bob = String::from("Bob");
171		let mut repr = Repr::with_msg(
172			Person {
173				name: bob.as_str().into(),
174				age: 25,
175			},
176			Person::is_valid,
177			"People must have a name and cannot be older than 200",
178		);
179		{
180			let person = repr.read();
181			assert_eq!("Bob", person.name);
182			assert_eq!(25, person.age);
183		}
184		{
185			let mut person = repr.write();
186			person.name = "Alice".into();
187			person.age = 200;
188		}
189		{
190			let person = repr.read();
191			assert_eq!("Alice", person.name);
192			assert_eq!(30, person.age);
193		}
194	}
195
196	#[test]
197	fn should_read_from_cache() {
198		let mut repr = CacheableRepr::new(
199			MinMax { min: 1, max: 5 },
200			|mm| mm.min < mm.max,
201		);
202		fn get_min(mm: &MinMax) -> i32 { mm.min }
203		assert_eq!(1, repr.lazy(get_min));
204		assert_eq!(1, repr.lazy(get_min));
205	}
206	#[test]
207	fn should_invalidate_cache_on_mutation() {
208		let mut repr = CacheableRepr::new(
209			MinMax { min: 1, max: 5 },
210			|mm| mm.min < mm.max,
211		);
212		fn get_min(mm: &MinMax) -> i32 {
213			mm.min
214		}
215		assert_eq!(1, repr.lazy(get_min));
216		assert_eq!(1, repr.lazy(get_min));
217		repr.write().min = 4;
218		assert_eq!(4, repr.lazy(get_min));
219		assert_eq!(4, repr.lazy(get_min));
220	}
221
222	#[test]
223	fn should_allow_static_closures_for_cache_reads() {
224		let mut repr = CacheableRepr::new(
225			MinMax { min: 1, max: 5 },
226			|mm| mm.min < mm.max,
227		);
228		assert_eq!(1, repr.lazy(|mm| mm.min));
229		assert_eq!(1, repr.lazy(|mm| mm.min));
230		repr.write().min = 4;
231		assert_eq!(4, repr.lazy(|mm| mm.min));
232		assert_eq!(4, repr.lazy(|mm| mm.min));
233	}
234
235	#[test]
236	fn should_hash_by_inner() {
237		let mut repr1 = Repr::new(
238			MinMax { min: 1, max: 5 },
239			|mm| mm.min < mm.max,
240		);
241		let mut repr2 = Repr::new(
242			MinMax { min: 1, max: 5 },
243			|mm| mm.min < mm.max,
244		);
245		let mut map = HashMap::new();
246		let repr1_w: &mut MinMax = &mut repr1.write();
247		let repr2_w: &mut MinMax = &mut repr2.write();
248		map.insert(&repr1_w, 1);
249		assert!(map.contains_key(&repr1_w));
250		assert!(map.contains_key(&repr2_w));
251		repr2_w.max = 10;
252		assert!(map.contains_key(&repr1_w));
253		assert!(!map.contains_key(&repr2_w));
254	}
255
256	#[test]
257	fn should_be_moveable_across_threads() {
258		let mut repr = Repr::new(
259			MinMax { min: 1, max: 5 },
260			|mm| mm.min < mm.max,
261		);
262		fn get_min(mm: &MinMax) -> i32 { mm.min }
263		fn get_max(mm: &MinMax) -> i32 { mm.max }
264
265		{
266			let mm = repr.as_ref();
267			assert_eq!(1, get_min(mm));
268			assert_eq!(5, get_max(mm));
269		}
270		{
271			let mut mm = repr.write();
272			mm.max = 100;
273			mm.min = 50;
274		}
275		std::thread::spawn(move || {
276			let mm = repr.as_ref();
277			assert_eq!(50, get_min(mm));
278			assert_eq!(100, get_max(mm));
279		}).join().unwrap();
280	}
281
282	#[tokio::test(flavor = "multi_thread")]
283	async fn should_be_moveable_across_tasks() {
284		let mut repr = Repr::new(
285			MinMax { min: 1, max: 5 },
286			|mm| mm.min < mm.max,
287		);
288		fn get_min(mm: &MinMax) -> i32 { mm.min }
289		fn get_max(mm: &MinMax) -> i32 { mm.max }
290
291		{
292			let mm = repr.as_ref();
293			assert_eq!(1, get_min(mm));
294			assert_eq!(5, get_max(mm));
295		}
296		{
297			let mut mm = repr.write();
298			mm.max = 100;
299			mm.min = 50;
300		}
301		tokio::spawn(async move {
302			let mm = repr.as_ref();
303			assert_eq!(50, get_min(mm));
304			assert_eq!(100, get_max(mm));
305		}).await.unwrap();
306	}
307
308	#[tokio::test(flavor = "multi_thread")]
309	async fn should_be_shareable_across_threads() {
310		let mut repr = Repr::new(
311			MinMax { min: 1, max: 5 },
312			|mm| mm.min < mm.max,
313		);
314		fn get_min(mm: &MinMax) -> i32 { mm.min }
315		fn get_max(mm: &MinMax) -> i32 { mm.max }
316
317		{
318			let mm = repr.as_ref();
319			assert_eq!(1, get_min(mm));
320			assert_eq!(5, get_max(mm));
321		}
322		{
323			let mut mm = repr.write();
324			mm.max = 100;
325			mm.min = 50;
326		}
327		std::thread::scope(|s| {
328			s.spawn(|| {
329				let mm = repr.as_ref();
330				assert_eq!(50, get_min(mm));
331				assert_eq!(100, get_max(mm));
332			});
333		});
334	}
335
336	#[tokio::test(flavor = "multi_thread")]
337	async fn should_work_with_shared_ownership_and_rwlock() {
338		let repr = Arc::new(RwLock::new(Repr::new(
339			MinMax { min: 1, max: 5 },
340			|mm| mm.min < mm.max,
341		)));
342		fn get_min(mm: &MinMax) -> i32 { mm.min }
343		fn get_max(mm: &MinMax) -> i32 { mm.max }
344
345		{
346			let lock = repr.read().await;
347			let mm = lock.read();
348			assert_eq!(1, get_min(mm));
349			assert_eq!(5, get_max(mm));
350		}
351		{
352			let mut lock = repr.write().await;
353			let mut mm = lock.write();
354			mm.max = 100;
355			mm.min = 50;
356		}
357		let r = repr.clone();
358		tokio::spawn(async move {
359			let mut lock = r.write().await;
360			let mut mm = lock.write();
361			assert_eq!(50, get_min(&mm));
362			assert_eq!(100, get_max(&mm));
363			mm.min = 10;
364			mm.max = 20;
365		}).await.unwrap();
366		{
367			let lock = repr.read().await;
368			let mm = lock.read();
369			assert_eq!(10, get_min(mm));
370			assert_eq!(20, get_max(mm));
371		}
372	}
373
374	#[cfg(feature = "eager")]
375	mod eager {
376		use std::sync::atomic::{AtomicU32, Ordering};
377		use std::time::Duration;
378		use crate::tests::MinMax;
379		use crate::{CacheableRepr, EagerCacheLookup};
380
381		#[tokio::test(flavor = "multi_thread")]
382		async fn should_read_from_cache() {
383			let mut repr = CacheableRepr::new(
384				MinMax { min: 1, max: 5 },
385				|mm| mm.min < mm.max,
386			);
387			fn get_min(mm: &MinMax) -> i32 {
388				mm.min
389			}
390			assert_eq!(1, repr.eager(get_min).await);
391			assert_eq!(1, repr.eager(get_min).await);
392			// repr.borrow_mut()n = 4;
393		}
394		#[tokio::test(flavor = "multi_thread")]
395		async fn should_invalidate_cache_on_mutation() {
396			let mut repr = CacheableRepr::new(
397				MinMax { min: 1, max: 5 },
398				|mm| mm.min < mm.max,
399			);
400			fn get_min(mm: &MinMax) -> i32 {
401				mm.min
402			}
403			assert_eq!(1, repr.eager(get_min).await);
404			assert_eq!(1, repr.eager(get_min).await);
405			repr.write().min = 4;
406			assert_eq!(4, repr.eager(get_min).await);
407			assert_eq!(4, repr.eager(get_min).await);
408		}
409
410		#[tokio::test(flavor = "multi_thread")]
411		async fn should_allow_static_closures_for_cache_reads() {
412			let mut repr = CacheableRepr::new(
413				MinMax { min: 1, max: 5 },
414				|mm| mm.min < mm.max,
415			);
416			assert_eq!(1, repr.eager(|mm| mm.min).await);
417			assert_eq!(1, repr.eager(|mm| mm.min).await);
418			repr.write().min = 4;
419			assert_eq!(4, repr.eager(|mm| mm.min).await);
420			assert_eq!(4, repr.eager(|mm| mm.min).await);
421		}
422
423		#[ignore]
424		#[tokio::test(flavor = "multi_thread")]
425		async fn should_work_with_expensive_computations() {
426			let mut repr = CacheableRepr::new(
427				MinMax { min: 1, max: 40 },
428				|mm| mm.min < mm.max,
429			);
430
431			fn fib(n: u64) -> u64 {
432				if n <= 1 {
433					return n;
434				}
435				fib(n - 1) + fib(n - 2)
436			}
437			fn plain_fib(mm: &MinMax) -> u64 { fib(mm.max as u64) }
438			fn plus2(mm: &MinMax) -> u64 { fib((mm.max + 2) as u64) }
439			fn plus3(mm: &MinMax) -> u64 { fib((mm.max + 3) as u64) }
440			fn plus4(mm: &MinMax) -> u64 { fib((mm.max + 4) as u64) }
441			fn plus5(mm: &MinMax) -> u64 { fib((mm.max + 5) as u64) }
442
443			assert_eq!(102334155, repr.eager(plain_fib).await);
444			assert_eq!(267914296, repr.eager(plus2).await);
445			assert_eq!(433494437, repr.eager(plus3).await);
446			assert_eq!(701408733, repr.eager(plus4).await);
447			assert_eq!(1134903170, repr.eager(plus5).await);
448			repr.write().max = 42;
449			assert_eq!(267914296, repr.eager(plain_fib).await);
450		}
451
452		#[tokio::test(flavor = "multi_thread")]
453		async fn should_be_able_to_unregister_caches() {
454			let mut repr = CacheableRepr::new(
455				MinMax { min: 1, max: 5 },
456				|mm| mm.min < mm.max,
457			);
458			fn get_min(mm: &MinMax) -> i32 {
459				mm.min
460			}
461			fn get_min2(mm: &MinMax) -> i32 {
462				mm.min
463			}
464			assert_eq!(1, repr.eager(get_min).await);
465			assert_eq!(1, repr.eager(get_min2).await);
466			assert_eq!(1, repr.eager(get_min).await);
467			assert!(repr.unregister(get_min2));
468		}
469
470		#[ignore]
471		#[tokio::test(flavor = "multi_thread")]
472		#[should_panic]
473		async fn should_propagate_panic_in_eager_cache() {
474			let mut repr = CacheableRepr::new(
475				MinMax { min: 1, max: 5 },
476				|mm| mm.min < mm.max,
477			);
478			fn get_min(mm: &MinMax) -> i32 {
479				mm.min
480			}
481			fn get_min2(mm: &MinMax) -> i32 {
482				if mm.min == 1 {
483					mm.min
484				} else {
485					panic!("random panic")
486				}
487			}
488			assert_eq!(1, repr.eager(get_min).await);
489			assert_eq!(1, repr.eager(get_min2).await);
490			assert_eq!(1, repr.eager(get_min).await);
491			repr.write().min = 2;
492		}
493
494		#[tokio::test(flavor = "multi_thread")]
495		async fn counting_eager_cache_hits() {
496			#[derive(Debug, Clone)]
497			struct Person { name: String }
498			let mut repr = CacheableRepr::new(Person { name: "Alice and Bob together at last".into() }, |p| !p.name.is_empty());
499			static READ_SPY: AtomicU32 = AtomicU32::new(0);
500			fn expensive_read(p: &Person) -> usize {
501			  // Just for demonstration purposes.
502			  // Do not do side effects in your read functions!
503			  READ_SPY.fetch_add(1, Ordering::Relaxed);
504			  fib(p.name.len())
505			}
506			let fib_of_name_len = repr.eager(expensive_read).await;
507			assert_eq!(832040, fib_of_name_len);
508			// this does not recompute the fibonacci number, it just gets it from the cache!
509			let fib_of_name_len2 = repr.eager(expensive_read).await;
510			assert_eq!(832040, fib_of_name_len2);
511			assert_eq!(1, READ_SPY.load(Ordering::Relaxed));
512			repr.write().name = "Alice".into();
513			// if we wait a bit we can see that a new value has been computed
514			tokio::time::sleep(Duration::from_millis(100)).await;
515			assert_eq!(2, READ_SPY.load(Ordering::Relaxed));
516			// Now when we fetch it again, we should see the new value without needing to recompute it
517			let fib_of_name_len3 = repr.eager(expensive_read).await;
518			assert_eq!(5, fib_of_name_len3);
519			assert_eq!(2, READ_SPY.load(Ordering::Relaxed));
520			fn fib(n: usize) -> usize {
521			  if n <= 1 { n } else { fib(n - 1) + fib(n - 2) }
522			}
523		}
524	}
525}