Finding entry in HashMap

This code works, but I have two questions in comments within the code.

use std::collections::HashMap;

type StringToIntMap = HashMap<String, i32>;

fn main() {
    let mut score_map: StringToIntMap = StringToIntMap::new();
    score_map.insert("Mark".to_string(), 19);
    score_map.insert("Tami".to_string(), 42);
    score_map.insert("Amanda".to_string(), 37);
    score_map.insert("Jeremy".to_string(), 35);
    
    // Why do I get the error "borrow of moved value" without "&"?
    for entry in &score_map {
        println!("{:?}", entry);
    }
    
    // Is this the best way to find the winner?
    // The use of "nobody" seems to complicated.
    let nobody = (&"".to_string(), &0i32);
    let winner = score_map.iter().fold(nobody, |acc, entry| {
       if entry.1 > &acc.1 { entry } else { acc }
    });
    println!("winner is {}", winner.0);
}

(Playground)

Output:

("Jeremy", 35)
("Mark", 19)
("Tami", 42)
("Amanda", 37)
winner is Tami

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.09s
     Running `target/debug/playground`

Why do I get the error "borrow of moved value" without &?

This is because a for loop always consumes the thing you iterator over, so without the &, the hash map is consumed. When you include the &, it just consumes the reference, which doesn't do much.

Is this the best way to find the winner?

No, it's simpler to use max_by_key

let winner = score_map.iter().max_by_key(|entry| entry.1).unwrap();
println!("winner is {}", winner.0);

Here the unwrap fails if score_map is empty.

4 Likes

Without it, you are passing ownership of the contents of the map to the loop body. So you couldn't reuse the map after the loop is complete.

You need to account for the case that there are no players in the game. Nonetheless, you can simplify this a bit by using max_by_key:

match score_map.iter().max_by_key(|(_name, score)| score) {
    None => unreachable!(),
    Some((name, _score)) => println!("winner is {}", name),
}

Hmm, I wonder why a for loop needs to consume what it being iterated over. Can you explain why that needs to happen?

I saw max_by_key, but decided not to use it because it is marked as experimental and only available in nightly. Is that right?

I don't see a nightly marker?

Edit: You are looking at some other method of the same name.

1 Like

As for the for loop, it's because if you need ownership of the values inside the map, you have to consume the map, and not using an & is the way you do that.

3 Likes

To elaborate on this, in for PAT in EXPR { ... }, the compiler creates an iterator by implicity inserting a call to {EXPR}.into_iter() (IntoIterator::into_iter). For collections, this creates an iterator that deconstructs the collection and yields the individual elements. However, for references to collections, this creates an iterator that merely iterates over references to elements.

3 Likes

And to make the final connection even more explicit, if you click on that IntoIterator::into_iter link you'll see that it consumes self.

I believe you, but I don’t see the word “consume” here: std::iter::IntoIterator - Rust
Am I looking at the wrong page?

You can see that it consumes it because the method takes self rather than &self or &mut self. Additionally, methods that start with into_ will by convention consume the thing it is called on.

3 Likes

Just checking my understanding ... Is it true that I can infer ownership is transferred because the first parameter is self AND HashMap does not implement the Copy trait? IIUC, if it did implement Copy then ownership would not be transferred.

Yes. Or both have ownership of their copied data, if you prefer.

(Incidentally, when making examples or experimenting with Rust's ownership model, it's always a good idea to do so with non-Copy types to ensure you don't mislead yourself or others.)

1 Like

Yeah, if a method takes self of a non-Copy type, then it consumes the value by moving ownership of the value into the function.

1 Like