What is the idiomatic way of solving this rustlings problem?

I am new to Rust Programming. This is from rustlings/hashmaps/hashmaps3.

struct Team {
    goals_scored: u8,
    goals_conceded: u8,
}

impl Team {
    fn new() -> Self {
        Team {
            goals_scored: 0,
            goals_conceded: 0,
        }
    }
}

fn build_scores_table(results: String) -> HashMap<String, Team> {
    let mut scores: HashMap<String, Team> = HashMap::new();
    let zero_team = Team::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 = 
        let Team { goals_scored, goals_conceded } = scores.get(&team_1_name)
            .unwrap_or(&zero_team);
        let new_score = Team {
            goals_scored: goals_scored + team_1_score,
            goals_conceded: goals_conceded + team_2_score,
        };
        scores.insert(team_1_name, new_score);
        let Team { goals_scored, goals_conceded } = scores.get(&team_2_name)
            .unwrap_or(&zero_team);
        let new_score = Team {
            goals_scored: goals_scored + team_2_score,
            goals_conceded: goals_conceded + team_1_score,
        };
        scores.insert(team_2_name, new_score);
    }
    scores
}

This code solves the issues, but I feel like I am missing some chunks of understanding here. And I have some questions:

  1. What is the meaning of zero_team , Is it like a singleton here?
  2. Am I shadowing goals_scored and goals_conceded or am I redefining them?
  3. What is the most idiomatic way to do this? How would you solve the problem?

No, why would it be? You can instantiate a Team as many times as you want. This has nothing to do with singletons. The zero_team is an additive identity element of the Team type, so you can use it for performing a no-op addition while accumulating the score.

But you don't need any of that. Since the Default impl for primitives already yields zero, you can just #[derive(Default)] on Team then call HashMap::entry().or_default(). All you need to solve this problem is:

        let team_1 = scores.entry(team_1_name).or_default();
        team_1.goals_scored += team_1_score;
        team_1.goals_conceded += team_2_score;

        let team_2 = scores.entry(team_2_name).or_default();
        team_2.goals_scored += team_2_score;
        team_2.goals_conceded += team_1_score;

Playground

2 Likes

Quick question: how are we able to manipulate team1 without it being marked mut here?

entry() returns access to entry in map for in-place manipulation, but how is that possible without team1 or team2 being mutable?

Because scores is mutable? Just because scores is mutable, doesn't mean that its entries are mutable too or does it?

team1 and team2 are of type &mut Team - you can either see it from the documentation for Entry::or_default, or by trying to set an obviously wrong type to them (like ()) and getting compiler error like this:

error[E0308]: mismatched types
  --> src/lib.rs:38:26
   |
38 |         let team_1: () = scores.entry(team_1_name).or_default();
   |                     --   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `&mut Team`

And if you have &mut T, you can mutate T freely.

3 Likes