user270199
user270199

Reputation: 1417

Closures exercise in Chapter 13 of the Rust book

I'm going through the Rust book and I'm currently a bit confused about my solution to the exercise in Chapter 13 on closures (https://doc.rust-lang.org/book/ch13-01-closures.html).

I want to apologise in advance if my questions don't make much sense. I still have a lot of things to learn. If my questions are too broad, please feel free to point me to specific documentation.

The code seems to run fine but I don't really understand the following:

  1. Why does self.map.get() take a reference? The map field is of type HashMap and this doesn't take a reference but u32.
  2. Do I always need to use Some(v) and None under match?
  3. Why do I need to dereference *v?
use std::thread;
use std::time::Duration;
use std::collections::HashMap;

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>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    // value: Option<u32>,
    map: HashMap<u32, u32>,
}

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        let map: HashMap<u32, u32> = HashMap::new();

        Cacher {
            calculation,
            map: map,
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.map.get(&arg) {
            Some(v) => *v,
            None => {
                let v = (self.calculation)(arg);
                self.map.insert(arg, v);
                v
            }
        }
    }
}

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

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

#[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);
}

Upvotes: 1

Views: 105

Answers (1)

SirDarius
SirDarius

Reputation: 42979

Why does self.map.get() take a reference?

Because the map does not need to take ownership of the key which is passed as parameter. The get method only needs to hash the key to eventually locate the entry, and compare it to the stored key to make sure it's not a hash collision. Here is the declaration of the get method:

pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V> where
    K: Borrow<Q>,
    Q: Hash + Eq

Do I always need to use Some(v) and None under match?

This question is a bit unclear. However if we focus on this specific case, since HashMap::get() can be called with a key which is not contained in the map, it needs a way for the caller to identify the case where the key has not been found. This is why the type Option exists.

Option is an enum type with two variants, Some(T) (value is present) and None (value is absent).

Since the match expression supports using enum variants as patterns, it's pretty common and idiomatic to see Option handling done that way.

Do you always need to use it that way? No, there are alternatives:

Generic pattern:

match self.map.get(&arg) {
    Some(v) => *v,
    _ => {
        let v = (self.calculation)(arg);
        self.map.insert(arg, v);
        v
    }
}

Since Option has only two variants, the None pattern can be replaced by the catch-all pattern _ which means "match everything except Some(v)". _ is often used as match expressions need to be exhaustive.

if let:

if let Some(v) = self.map.get(&arg) {
    *v
} else {
    let v = (self.calculation)(arg);
    self.map.insert(arg, v);
    v
}

Very similar to match when handling two cases only, but arguably more readable.

Entry API:

let e = self.map.entry(arg).or_insert_with(|| (self.calculation)(arg));
*e

This is a pretty convenient way of setting a HashMap entry if it is not present, and getting the value. Note that here, the map takes ownership of arg, because it might insert it into the HashMap.

Why do I need to dereference *v?

This is because get returns a reference to the value which is still owned by the map, so its type is &u32.

The ownership model is one of the most powerful aspects of memory management in Rust. At any point in the duration of the program, every object present in memory is guaranteed to have an owner.

If get returned the value itself, and not a reference, it would mean that the ownership of the value would be moved to the caller, and the map wouldn't be able to use it anymore.

In addition, it is worth mentioning that integers such as u32 are Copy types, which means that when they are derefenced like this, they are in fact copied, so that the value you return from your method is a copy of the value still owned by the map.

Upvotes: 2

Related Questions