2021-06-29

How do I create a closure that takes either u32 or &str and returns u32 or usize respectively?

This is from chapter 13.1 of the Rust book, where we use closures, memoization and generic types. In this example I can already pass two different closures to the struct (Cacher) and get the values accordingly, but they have two be two different instances of the struct in order to handle the types correctly.

Can I build a generic type so the same instance of the struct can handle receiving either type and give me the value accordingly? Either the same u32 I passed, or the length of the &str I passed.

use std::collections::HashMap;
use std::thread;
use std::time::Duration;

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_result.value(intensity));
        println!("Next, do {} situps!", expensive_result.value(intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.value(intensity)
            );
        }
    }
}

struct Cacher<T, U, V>
where
    T: Fn(U) -> V,
{
    calculation: T,
    values: HashMap<U, Option<V>>,
}

impl<T, U, V> Cacher<T, U, V>
// Generic T is the function where U and V are the parameter and return value respectively for said function.
where
    T: Fn(U) -> V,
    U: std::cmp::Eq // U must have 3 trait bounds, Eq, Hash, and Copy.
        + std::hash::Hash
        + Copy,
    V: Copy, // V must have trait bound Copy.
{
    fn new(calculation: T) -> Cacher<T, U, V> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }

    fn value(&mut self, arg: U) -> V {
        match self.values.get(&arg) {
            // Get the value for the key in the hashmap.
            Some(v) => v.unwrap(), // get the value in the option stored in the hashmap.
            None => {
                let v = (self.calculation)(arg);
                self.values.insert(arg, Some(v)); // Put the key/value pair into the hashmap.
                v
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Cacher;
    #[test]
    fn call_with_different_values() {
        let mut c = Cacher::new(|a| a);

        let _v1 = c.value(1);
        let v2 = c.value(2);

        assert_eq!(v2, 2);
    }

    #[test]
    fn call_with_string_usize() {
        let mut c = Cacher::new(|a: &str| a.len());

        let v1 = c.value("Three");

        assert_eq!(v1, 5);
    }
}

I tried building structs for the value field of the struct and then implementing a trait of that would group them:

struct CStr<'a> {
    value: &'a str,
}

struct CInt {
    value: u32,
}

trait CValue {
    fn get<W: Copy + Eq>(&self) -> W;
}

impl<'a> CValue for CStr<'a> {
    fn get<W: Copy + Eq>(&self) -> W {
        self.value.len()
    }
}

So this test would pass

#[test]
fn call_with_different_values() {
    let mut c = Cacher::new(|a| a.get());

    let v1 = c.value(1);
    let v2 = c.value("two");

    assert_eq!(v2, 3);
}

but I am stuck here:

impl<'a> CValue for CStr<'a> {
    fn get<W: Copy + Eq>(&self) -> W {
        self.value.len() // here
    }
}

with the error:

[rustc E0308] [E] mismatched types expected type parameter `W` found type `usize`


from Recent Questions - Stack Overflow https://ift.tt/3606NC8
https://ift.tt/eA8V8J

No comments:

Post a Comment