Being an aspiring rustacean, I've been working my way through The Rust Programming Language book and being in the 13th chapter I was attempting to generalize the Cacher struct, that has as a purpose implementing lazy evaluation around a closure. While I was able to use generics to generalize the closure signature to any one parameter with any one output type, I can't figure out how to generalize this to closures with any number of params. I feel like there should be a way to do this.
struct Cacher<'a, Args, V: Clone>
{
calculation: &'a dyn Fn(Args) -> V,
value: Option<V>
}
impl<'a, Args, V: Clone> Cacher<'a, Args, V>
{
fn new(calculation: &'a dyn Fn(Args) -> V) -> Cacher<Args, V> {
Cacher {
calculation: calculation,
value: None,
}
}
fn value(&mut self, arg: Args) -> V {
// all this cloning is probably not the best way to do this
match self.value.clone() {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v.clone());
v
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let mut cached_func = Cacher::new(&(|asd| asd + 1));
assert_eq!(cached_func.value(1), 2);
assert_eq!(cached_func.value(4), 2);
}
#[test]
fn it_works_too() {
// compiler hates this
let mut cached_func = Cacher::new(&(|asd, qwe| asd + qwe));
assert_eq!(cached_func.value(1, 1), 2);
assert_eq!(cached_func.value(4, 1), 2);
}
}
You can do this on nightly using the fn_traits (and closely related unboxed_closures) features. This allows you to use Fn like Fn<Args, Output = V> where Args is a tuple type of all the parameters passed to the function.
#![feature(unboxed_closures)]
#![feature(fn_traits)]
struct Cacher<'a, Args, V: Clone>
{
calculation: &'a dyn Fn<Args, Output = V>,
value: Option<V>
}
impl<'a, Args, V: Clone> Cacher<'a, Args, V>
{
fn new(calculation: &'a dyn Fn<Args, Output = V>) -> Cacher<Args, V> {
Cacher {
calculation: calculation,
value: None,
}
}
fn value(&mut self, args: Args) -> V {
// all this cloning is probably not the best way to do this
match self.value.clone() {
Some(v) => v,
None => {
let v = self.calculation.call(args);
self.value = Some(v.clone());
v
}
}
}
}
This does require you to call value() with a tuple:
let mut cache1 = Cacher::new(&|a| a + 1);
let value1 = cache1.value((7,));
let mut cache2 = Cacher::new(&|a, b| a + b);
let value2 = cache2.value((7, 8));
However, you can make it nicer to use if you're willing to make the boilerplate for the numerous tuple types:
impl<'a, T, V: Clone> Cacher<'a, (T,), V>
{
fn value2(&mut self, arg1: T) -> V {
self.value((arg1, ))
}
}
impl<'a, T, U, V: Clone> Cacher<'a, (T, U), V>
{
fn value2(&mut self, arg1: T, arg2: U) -> V {
self.value((arg1, arg2))
}
}
// ...
let mut cache1 = Cacher::new(&|a: usize| a + 1);
let value1 = cache1.value2(7);
let mut cache2 = Cacher::new(&|a: usize, b: usize| a + b);
let value2 = cache2.value2(7, 8);
See it running on the playground.
This only works on nightly because its not yet been stabilized if this is how they will be supported generically in the future.
In rust, functions do not have a variable numbers of arguments, except in some cases for compatibility with C. This answer provides more background.
In your example, you could achieve some generic lazy evaluation with the lazy static crate. You don’t pass a closure to this crate, not explicitly at least. But you put the body of the closure in a variable that lazy static evaluates on first access (a bit like a closure taking () and whose result would be stored in Cacher, if you will).
It's fairly hard to understand exactly what is it that you need. So here's my guess:
struct Cacher<'a, Args, V: Copy>
{
calculation: &'a dyn Fn(Args) -> V,
value: Option<V>
}
impl<'a, Args, V: Copy> Cacher<'a, Args, V>
{
fn new(calculation: &'a dyn Fn(Args) -> V) -> Cacher<Args, V> {
Cacher {
calculation: calculation,
value: None,
}
}
fn value(&mut self, arg: Args) -> V {
// Cloning fixed
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let mut cached_func = Cacher::new(&(|asd| asd + 1));
assert_eq!(cached_func.value(1), 2);
assert_eq!(cached_func.value(4), 2);
}
#[test]
fn it_works_too() {
// The compiler is fine
// Although now, it's not multiple arguments but rather one arg, acting as many
let mut cached_func = Cacher::new(&(|asd: (usize, usize)| asd.0 + asd.1));
assert_eq!(cached_func.value((1, 1)), 2);
assert_eq!(cached_func.value((4, 1)), 2);
}
}
Remember that Rust's generics could be considered as Algebraic Data Types, hence, only enums, structs and functions are allowed (closures too, if you consider them different to functions). The second test works because tuples could be considered structs.
Because of this, it's impossible to have multiple arguments in one function definition.
The usual way that rust solves this issue is with macros. Although method macros don't exist in rust yet.
Related
I was going through rust-by-practice and came across a problem. I have no idea what a particular line of code does. This is the exact piece of code for context:
fn example1() {
// `T: Trait` is the commonly used way.
// `T: Fn(u32) -> u32` specifies that we can only pass a closure to `T`.
struct Cacher<T: Fn(u32) -> u32> {
calculation: T,
value: Option<u32>,
}
impl<T: Fn(u32) -> u32> Cacher<T> {
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg); // This exact line of code
self.value = Some(v);
v
},
}
}
}
let mut cacher = Cacher::new(|x| x+1);
assert_eq!(cacher.value(10), __);
assert_eq!(cacher.value(15), __);
}
The calculation field of Cacher is a closure (type T where T: Fn(u32) -> u32), and that line is calling it with arg as an argument, then assigning the resulting u32 to v. Without the parentheses, let v = self.calculation(arg) will look for a method named calculation, which doesn't exist. The parenthesis force a different order of operations which produces a different result.
This is valid code:
use std::rc::Rc;
use std::sync::{Arc, Mutex};
fn foo(n: i32) {
println!("n = {}", n)
}
fn main() {
let a = 1;
let b = Rc::new(2);
let c = Mutex::new(3);
let d = Arc::new(Mutex::new(4));
foo(a);
foo(*b);
foo(*(c.lock().unwrap()));
foo(*((*d).lock().unwrap()));
}
Are there any traits (or anything else) that I can implement so that the function calls could simply become:
foo(a);
foo(b);
foo(c);
foo(d);
What is the Rust idiomatic way for handling the actual data and not caring about how the data is protected/wrapped?
As others have pointed out, it is a bad idea to ignore the fallibility of Mutex::lock. However for the other cases, you can make your function accept owned values and references transparently by using Borrow:
use std::borrow::Borrow;
use std::rc::Rc;
fn foo (n: impl Borrow<i32>) {
println!("n = {}", n.borrow())
}
fn main() {
let a = 1;
let b = Rc::new (2);
let c = &3;
foo (a);
foo (b);
foo (c);
}
Playground
Here is an extremely literal answer to your specific question. I wouldn't use it.
use std::{
rc::Rc,
sync::{Arc, Mutex},
};
fn foo(n: impl DontCare<Output = i32>) {
let n = n.gimme_it();
println!("n = {}", n)
}
fn main() {
let a = 1;
let b = Rc::new(2);
let c = Mutex::new(3);
let d = Arc::new(Mutex::new(4));
foo(a);
foo(b);
foo(c);
foo(d);
}
trait DontCare {
type Output;
fn gimme_it(self) -> Self::Output;
}
impl DontCare for i32 {
type Output = Self;
fn gimme_it(self) -> Self::Output {
self
}
}
impl<T: DontCare> DontCare for Mutex<T> {
type Output = T::Output;
fn gimme_it(self) -> Self::Output {
self.into_inner()
.expect("Lets first assume unwrap() will not panic")
.gimme_it()
}
}
impl<T: DontCare> DontCare for Rc<T> {
type Output = T::Output;
fn gimme_it(self) -> Self::Output {
match Rc::try_unwrap(self) {
Ok(v) => v.gimme_it(),
_ => unreachable!("Lets first assume unwrap() will not panic"),
}
}
}
impl<T: DontCare> DontCare for Arc<T> {
type Output = T::Output;
fn gimme_it(self) -> Self::Output {
match Arc::try_unwrap(self) {
Ok(v) => v.gimme_it(),
_ => unreachable!("Lets first assume unwrap() will not panic"),
}
}
}
The function signatures you've specified take ownership of the value. That will be highly painful, especially paired with any type that doesn't implement Copy.
There are a number of code paths that panic implicitly. I'm not a fan of baking in panics — I reserve that for algorithmic failures, not data-driven ones.
See also:
Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?
My answer is similar to Shepmaster's answer, but maybe a little more practical since it doesn't consume the argument to foo2 and it gives you a reference to the target instead of taking it out of the container.
I never have any luck implementing traits based on other traits so I didn't try to do that here.
use std::ops::Deref;
use std::rc::Rc;
use std::sync::{Arc, Mutex, MutexGuard};
fn foo(n: i32) {
println!("n = {}", n)
}
trait Holding<T> {
type Holder: Deref<Target = T>;
fn held(self) -> Self::Holder;
}
fn foo2<H: Holding<i32>>(x: H) {
let h = x.held();
println!("x = {}", *h);
}
impl<'a, T: 'a> Holding<T> for &'a T {
type Holder = &'a T;
fn held(self) -> Self::Holder {
self
}
}
impl<'a, T> Holding<T> for &'a Rc<T> {
type Holder = &'a T;
fn held(self) -> Self::Holder {
&**self
}
}
impl<'a, T> Holding<T> for &'a Mutex<T> {
type Holder = MutexGuard<'a, T>;
fn held(self) -> Self::Holder {
// this can panic
self.lock().unwrap()
}
}
impl<'a, T> Holding<T> for &'a Arc<Mutex<T>> {
type Holder = MutexGuard<'a, T>;
fn held(self) -> Self::Holder {
// this can panic
(*self).lock().unwrap()
}
}
fn main() {
let a = 1;
let b = Rc::new(2);
let c = Mutex::new(3);
let d = Arc::new(Mutex::new(4));
foo(a);
foo(*b);
foo(*(c.lock().unwrap()));
foo(*((*d).lock().unwrap()));
foo2(&a);
foo2(&b);
foo2(&c);
foo2(&d);
}
fn edit_map_values(
map1: &mut HashMap<String, i128> || &mut BTreeMap<String, i128>){
for tuple in map1.iter_mut() {
if !map1.contains_key(&"key1") {
*tuple.1 += 1;
}
}
map1.insert(&"key2", 10);
}
How do I write one function that accepts either HashMap and BtreeMap like in the example above?
It is possible to abstract over types by using traits and for your specific use-case, you can take a look at this more constrained example.
use core::{borrow::Borrow, hash::Hash};
use std::collections::{BTreeMap, HashMap};
trait GenericMap<K, V> {
fn contains_key<Q>(&self, k: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + Ord;
fn each_mut<F>(&mut self, cb: F)
where
F: FnMut((&K, &mut V));
fn insert(&mut self, key: K, value: V) -> Option<V>;
}
impl<K, V> GenericMap<K, V> for HashMap<K, V>
where
K: Eq + Hash,
{
fn contains_key<Q>(&self, k: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + Ord,
{
self.contains_key(k)
}
fn each_mut<F>(&mut self, mut cb: F)
where
F: FnMut((&K, &mut V)),
{
self.iter_mut().for_each(|x| cb(x))
}
fn insert(&mut self, key: K, value: V) -> Option<V> {
self.insert(key, value)
}
}
impl<K, V> GenericMap<K, V> for BTreeMap<K, V>
where
K: Ord,
{
fn contains_key<Q>(&self, k: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + Ord,
{
self.contains_key(k)
}
fn each_mut<F>(&mut self, mut cb: F)
where
F: FnMut((&K, &mut V)),
{
self.iter_mut().for_each(|x| cb(x))
}
fn insert(&mut self, key: K, value: V) -> Option<V> {
self.insert(key, value)
}
}
fn edit_map_values<T: GenericMap<String, i128>>(map: &mut T) {
map.each_mut(|(k, v)| {
if k != "key1" {
*v += 1;
}
});
map.insert("key2".into(), 10);
}
fn main() {
let mut hm: HashMap<String, i128> = [("One".into(), 1), ("Two".into(), 2)]
.iter()
.cloned()
.collect();
let mut btm: BTreeMap<String, i128> = [("Five".into(), 5), ("Six".into(), 6)]
.iter()
.cloned()
.collect();
dbg!(&hm);
dbg!(&btm);
edit_map_values(&mut hm);
edit_map_values(&mut btm);
dbg!(&hm);
dbg!(&btm);
}
Way back before the 1.0 release, there used to be Map and MutableMap traits, but they have been removed before stabilization. The Rust type system is currently unable to express these traits in a nice way due to the lack of higher kinded types.
The eclectic crate provides experimental collection traits, but they haven't been updated for a year, so I'm not sure they are still useful for recent versions of Rust.
Further information:
Does Rust have Collection traits?
No common trait for Map types? (Rust language forum)
Associated type constructors, part 1: basic concepts and introduction (blog post by Niko Matsakis)
Generic associated type RFC
While there is no common Map trait, you could use a combination of other traits to operate on an Iterator to achieve similar functionality. Although this might not be very memory efficient due to cloning, and also a bit involved depending on the kind of operation you are trying to perform. The operation you tried to do may be implemented like this:
fn edit_map_values<I>(map: &mut I)
where
I: Clone + IntoIterator<Item = (String, i128)> + std::iter::FromIterator<(String, i128)>,
{
// Since into_iter consumes self, we have to clone here.
let (keys, _values): (Vec<String>, Vec<_>) = map.clone().into_iter().unzip();
*map = map
.clone()
.into_iter()
// iterating while mutating entries can be done with map
.map(|mut tuple| {
if !keys.contains(&"key1".to_string()) {
tuple.1 += 1;
}
tuple
})
// inserting an element can be done with chain and once
.chain(std::iter::once(("key2".into(), 10)))
.collect();
// removing an element could be done with filter
// removing and altering elements could be done with filter_map
// etc.
}
fn main() {
use std::collections::{BTreeMap, HashMap};
{
let mut m = HashMap::new();
m.insert("a".to_string(), 0);
m.insert("key3".to_string(), 1);
edit_map_values(&mut m);
println!("{:#?}", m);
}
{
let mut m = BTreeMap::new();
m.insert("a".to_string(), 0);
m.insert("key3".to_string(), 1);
edit_map_values(&mut m);
println!("{:#?}", m);
}
}
Both times the output is the same, except for the order of the HashMap of course:
{
"a": 1,
"key2": 10,
"key3": 2,
}
I have this code:
use std::fmt::Debug;
struct S<A>
where
for<'a> A: Debug + 'a,
{
f: Box<Fn(A) -> i32>,
}
impl<A> S<A>
where
for<'a> A: Debug + 'a,
{
fn call(&self, a: A) {
println!("Return {:?}", (self.f)(a));
}
}
fn create<A>(f: Box<Fn(A) -> i32>) -> S<A>
where
for<'a> A: Debug + 'a,
{
S::<A> { f }
}
fn helper() {
let x = create::<&i32>(Box::new(|x: &i32| *x * 2));
let arg = 333;
x.call(&arg);
}
fn main() {
let x = helper();
}
It's failed to compile:
error[E0310]: the parameter type `A` may not live long enough
In code 2, I changed Fn(A) -> i32 to Fn(&A) -> i32, the code works.
...
f: Box<Fn(&A) -> i32>,
...
Since A is argument of Fn trait, it's a type that has Higher-Rank lifetime. It shouldn't be affected by the lifetime of struct S<A> .
But why can't code 1 be compiled?
How can I workaround it for borrow or non-borrow type A?
There is no easy way to make helper work in current Rust, even if you remove all the for<'a> A: Debug + 'a, bounds (which only further restricts what types A can be, whereas you want to allow more).
This is as simple as I can make your example:
struct S<A> {
f: Box<Fn(A) -> i32>,
}
impl<A> S<A> {
fn call(&self, a: A) {
println!("Return {:?}", (self.f)(a));
}
}
fn create<A>(f: Box<Fn(A) -> i32>) -> S<A> {
S { f }
}
fn helper() {
let x = create(Box::new(|x: &i32| *x * 2));
let arg = 333;
x.call(&arg);
}
fn main() {
helper();
}
The reason it doesn't work is that A "comes from the outside", and Rust can't infer that you want for<'a> S<&'a A>, it can't even talk about such a type.
Note that if let arg = 333; is placed above let x, this example does compile (because it infers a reference to arg specifically, not a for<'a>).
The closest you can get today is with an associated type on a trait with a lifetime parameter, e.g.:
// Emulating `type Type<'a>` by moving `'a` to the trait.
trait Apply<'a> {
type Type;
}
struct Plain<T>(std::marker::PhantomData<T>);
impl<'a, T> Apply<'a> for Plain<T> {
type Type = T;
}
struct Ref<T: ?Sized>(std::marker::PhantomData<T>);
impl<'a, T: ?Sized + 'a> Apply<'a> for Ref<T> {
type Type = &'a T;
}
struct S<A: for<'a> Apply<'a>> {
f: Box<for<'a> Fn(<A as Apply<'a>>::Type) -> i32>,
}
impl<A: for<'a> Apply<'a>> S<A> {
fn call<'a>(&self, a: <A as Apply<'a>>::Type) {
println!("Return {:?}", (self.f)(a));
}
}
fn create<A: for<'a> Apply<'a>>(
f: Box<for<'a> Fn(<A as Apply<'a>>::Type) -> i32>,
) -> S<A> {
S { f }
}
fn helper() {
let x = create::<Ref<i32>>(Box::new(|x: &i32| *x * 2));
let arg = 333;
x.call(&arg);
}
fn main() {
helper();
}
However, it turns out that this encoding hits https://github.com/rust-lang/rust/issues/52812, so it's not actually usable at the moment (and I'm not aware of an workaround).
I am reading the section on closures in the second edition of the Rust book. At the end of this section, there is an exercise to extend the Cacher implementation given before. I gave it a try:
use std::clone::Clone;
use std::cmp::Eq;
use std::collections::HashMap;
use std::hash::Hash;
struct Cacher<T, K, V>
where
T: Fn(K) -> V,
K: Eq + Hash + Clone,
V: Clone,
{
calculation: T,
values: HashMap<K, V>,
}
impl<T, K, V> Cacher<T, K, V>
where
T: Fn(K) -> V,
K: Eq + Hash + Clone,
V: Clone,
{
fn new(calculation: T) -> Cacher<T, K, V> {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value(&mut self, arg: K) -> V {
match self.values.clone().get(&arg) {
Some(v) => v.clone(),
None => {
self.values
.insert(arg.clone(), (self.calculation)(arg.clone()));
self.values.get(&arg).unwrap().clone()
}
}
}
}
After creating a version that finally works, I am really unhappy with it. What really bugs me is that cacher.value(...) has 5(!) calls to clone() in it. Is there a way to avoid this?
Your suspicion is correct, the code contains too many calls to clone(), defeating the very optimizations Cacher is designed to achieve.
Cloning the entire cache
The one to start with is the call to self.values.clone() - it creates a copy of the entire cache on every single access.
After non-lexical lifetimes
Remove this clone.
Before non-lexical lifetimes
As you likely discovered yourself, simply removing .clone() doesn't compile. This is because the borrow checker considers the map referenced for the entire duration of match. The shared reference returned by HashMap::get points to the item inside the map, which means that while it exists, it is forbidden to create another mutable reference to the same map, which is required by HashMap::insert. For the code to compile, you need to split up the match in order to force the shared reference to go out of scope before insert is invoked:
// avoids unnecessary clone of the whole map
fn value(&mut self, arg: K) -> V {
if let Some(v) = self.values.get(&arg).map(V::clone) {
return v;
} else {
let v = (self.calculation)(arg.clone());
self.values.insert(arg, v.clone());
v
}
}
This is much better and probably "good enough" for most practical purposes. The hot path, where the value is already cached, now consists of only a single clone, and that one is actually necessary because the original value must remain in the hash map. (Also, note that cloning doesn't need to be expensive or imply deep copying - the stored value can be an Rc<RealValue>, which buys object sharing for free. In that case, clone() will simply increment the reference count on the object.)
Clone on cache miss
In case of cache miss, the key must be cloned, because calculation is declared to consume it. A single cloning will be sufficient, though, so we can pass the original arg to insert without cloning it again. The key clone still feels unnecessary, though - a calculation function shouldn't require ownership of the key it is transforming. Removing this clone boils down to modifying the signature of the calculation function to take the key by reference. Changing the trait bounds of T to T: Fn(&K) -> V allows the following formulation of value():
// avoids unnecessary clone of the key
fn value(&mut self, arg: K) -> V {
if let Some(v) = self.values.get(&arg).map(V::clone) {
return v;
} else {
let v = (self.calculation)(&arg);
self.values.insert(arg, v.clone());
v
}
}
Avoiding double lookups
Now are left with exactly two calls to clone(), one in each code path. This is optimal, as far as value cloning is concerned, but the careful reader will still be nagged by one detail: in case of cache miss, the hash table lookup will effectively happen twice for the same key: once in the call to HashMap::get, and then once more in HashMap::insert. It would be nice if we could instead reuse the work done the first time and perform only one hash map lookup. This can be achieved by replacing get() and insert() with entry():
// avoids the second lookup on cache miss
fn value(&mut self, arg: K) -> V {
match self.values.entry(arg) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let v = (self.calculation)(entry.key());
entry.insert(v)
}
}.clone()
}
We've also taken the opportunity to move the .clone() call after the match.
Runnable example in the playground.
I was solving the same exercise and ended with the following code:
use std::thread;
use std::time::Duration;
use std::collections::HashMap;
use std::hash::Hash;
use std::fmt::Display;
struct Cacher<P, R, T>
where
T: Fn(&P) -> R,
P: Eq + Hash + Clone,
{
calculation: T,
values: HashMap<P, R>,
}
impl<P, R, T> Cacher<P, R, T>
where
T: Fn(&P) -> R,
P: Eq + Hash + Clone,
{
fn new(calculation: T) -> Cacher<P, R, T> {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value<'a>(&'a mut self, key: P) -> &'a R {
let calculation = &self.calculation;
let key_copy = key.clone();
self.values
.entry(key_copy)
.or_insert_with(|| (calculation)(&key))
}
}
It only makes a single copy of the key in the value() method. It does not copy the resulting value, but instead returns a reference with a lifetime specifier, which is equal to the lifetime of the enclosing Cacher instance (which is logical, I think, because values in the map will continue to exist until the Cacher itself is dropped).
Here's a test program:
fn main() {
let mut cacher1 = Cacher::new(|num: &u32| -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
*num
});
calculate_and_print(10, &mut cacher1);
calculate_and_print(20, &mut cacher1);
calculate_and_print(10, &mut cacher1);
let mut cacher2 = Cacher::new(|str: &&str| -> usize {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
str.len()
});
calculate_and_print("abc", &mut cacher2);
calculate_and_print("defghi", &mut cacher2);
calculate_and_print("abc", &mut cacher2);
}
fn calculate_and_print<P, R, T>(intensity: P, cacher: &mut Cacher<P, R, T>)
where
T: Fn(&P) -> R,
P: Eq + Hash + Clone,
R: Display,
{
println!("{}", cacher.value(intensity));
}
And its output:
calculating slowly...
10
calculating slowly...
20
10
calculating slowly...
3
calculating slowly...
6
3
If you remove the requirement of returning values, you don't need to perform any clones by making use of the Entry:
use std::{
collections::{hash_map::Entry, HashMap},
fmt::Display,
hash::Hash,
thread,
time::Duration,
};
struct Cacher<P, R, T>
where
T: Fn(&P) -> R,
P: Eq + Hash,
{
calculation: T,
values: HashMap<P, R>,
}
impl<P, R, T> Cacher<P, R, T>
where
T: Fn(&P) -> R,
P: Eq + Hash,
{
fn new(calculation: T) -> Cacher<P, R, T> {
Cacher {
calculation,
values: HashMap::new(),
}
}
fn value<'a>(&'a mut self, key: P) -> &'a R {
let calculation = &self.calculation;
match self.values.entry(key) {
Entry::Occupied(e) => e.into_mut(),
Entry::Vacant(e) => {
let result = (calculation)(e.key());
e.insert(result)
}
}
}
}
fn main() {
let mut cacher1 = Cacher::new(|num: &u32| -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(1));
*num
});
calculate_and_print(10, &mut cacher1);
calculate_and_print(20, &mut cacher1);
calculate_and_print(10, &mut cacher1);
let mut cacher2 = Cacher::new(|str: &&str| -> usize {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
str.len()
});
calculate_and_print("abc", &mut cacher2);
calculate_and_print("defghi", &mut cacher2);
calculate_and_print("abc", &mut cacher2);
}
fn calculate_and_print<P, R, T>(intensity: P, cacher: &mut Cacher<P, R, T>)
where
T: Fn(&P) -> R,
P: Eq + Hash,
R: Display,
{
println!("{}", cacher.value(intensity));
}
You could then choose to wrap this in another struct that performed a clone:
struct ValueCacher<P, R, T>
where
T: Fn(&P) -> R,
P: Eq + Hash,
R: Clone,
{
cacher: Cacher<P, R, T>,
}
impl<P, R, T> ValueCacher<P, R, T>
where
T: Fn(&P) -> R,
P: Eq + Hash,
R: Clone,
{
fn new(calculation: T) -> Self {
Self {
cacher: Cacher::new(calculation),
}
}
fn value(&mut self, key: P) -> R {
self.cacher.value(key).clone()
}
}