I'm doing the rustlings exercises, and I've arrived at a simple question after doing one of the exercises, but I'm also not sure I'm not doing something else wrong.
Here's the question: is it the case that Rust does support indexing with the square brackets to return something mutable, but that HashMap<K, V>
doesn't implement it? And if that's not the case, what did I do wrong?
Here's the whole exercise for reference:
fn build_scores_table(results: String) -> HashMap<String, Team> {
// The name of the team is the key and its associated struct is the value.
let mut scores: HashMap<String, Team> = HashMap::new();
for r in results.lines() {
let v: Vec<&str> = r.split(',').collect();
let team_1_name = v[0].to_string();
let team_1_score: u8 = v[2].parse().unwrap();
let team_2_name = v[1].to_string();
let team_2_score: u8 = v[3].parse().unwrap();
// TODO: Populate the scores table with details extracted from the
// current line. Keep in mind that goals scored by team_1
// will be the number of goals conceded from team_2, and similarly
// goals scored by team_2 will be the number of goals conceded by
// team_1.
}
scores
}
I tried a few different approaches and found one that compiles. It feels a little weird to me, but I think that's a good sign because I'm new to rust and because it looks clean, even if it feels unconventional to me.
let team1 = scores.get_mut(&team_1_name);
match team1 {
Some(t) => {
t.goals_scored += team_1_score;
t.goals_conceded += team_2_score;
},
None => {
scores.insert(team_1_name, Team {
goals_scored: team_1_score,
goals_conceded: team_2_score
});
}
}
This, however, does not compile. The answer may be that the square bracket notation, which seems to be syntactic sugar for index
, doesn't return a mutable, but I want to check if that's right.
if ! scores.contains_key(&team_2_name) {
scores.insert(team_2_name.clone(), Team { goals_conceded: 0, goals_scored: 0 });
}
let team2 = scores[&team_2_name];
team2.goals_scored += team_2_score;
team2.goals_conceded += team_1_score;
I'm trying to let the compiler teach me. I know that let team2...
line is wrong because it's not mutable, but I thought the compiler might give me a good hint.
error[E0594]: cannot assign to `team2.goals_scored`, as `team2` is not declared as mutable
--> exercises/hashmaps/hashmaps3.rs:62:9
|
62 | team2.goals_scored += team_2_score;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign
|
help: consider changing this to be mutable
|
61 | let mut team2 = scores[&team_2_name];
| +++
That gets me:
error[E0507]: cannot move out of index of `HashMap<String, Team>`
--> exercises/hashmaps/hashmaps3.rs:61:25
|
61 | let mut team2 = scores[&team_2_name];
| ^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `Team`, which does not implement the `Copy` trait
|
help: consider borrowing here
|
61 | let mut team2 = &scores[&team_2_name];
| +
Then I get:
warning: variable does not need to be mutable
--> exercises/hashmaps/hashmaps3.rs:61:13
|
61 | let mut team2 = &scores[&team_2_name];
| ----^^^^^
| |
| help: remove this `mut`
|
= note: `#[warn(unused_mut)]` on by default
error[E0594]: cannot assign to `team2.goals_scored`, which is behind a `&` reference
--> exercises/hashmaps/hashmaps3.rs:62:9
|
62 | team2.goals_scored += team_2_score;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `team2` is a `&` reference, so the data it refers to cannot be written
Next is:
error[E0594]: cannot assign to `team2.goals_scored`, which is behind a `&` reference
--> exercises/hashmaps/hashmaps3.rs:62:9
|
62 | team2.goals_scored += team_2_score;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `team2` is a `&` reference, so the data it refers to cannot be written
|
help: consider changing this to be a mutable reference
|
61 | let team2 = &mut scores[&team_2_name];
| +++
error[E0594]: cannot assign to `team2.goals_conceded`, which is behind a `&` reference
--> exercises/hashmaps/hashmaps3.rs:63:9
|
63 | team2.goals_conceded += team_1_score;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `team2` is a `&` reference, so the data it refers to cannot be written
|
help: consider changing this to be a mutable reference
|
61 | let team2 = &mut scores[&team_2_name];
| +++
Lastly, I get:
error[E0596]: cannot borrow data in an index of `HashMap<String, Team>` as mutable
--> exercises/hashmaps/hashmaps3.rs:61:21
|
61 | let team2 = &mut scores[&team_2_name];
| ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
|
= help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `HashMap<String, Team>`
= help: to modify a `HashMap<String, Team>`, use `.get_mut()`, `.insert()` or the entry API
Interestingly, I think it's suggesting to do something similar to what I did for the first solution.
Rust supports two ways (traits) to index with []
:
Index returns an immutable reference - useful if one only wants to read the content.
IndexMut returns a mutable reference - useful if one wants to change the contents at a certain index.
HashMap
does not implement IndexMut
, probably because the contract of indexing (panic if entry does not exist) leads to misunderstandings and bad workarounds.
HashMap
instead provides get_mut
or entry
.