Beginner - Fighting the borrow checker

Hi, classic question today as I've been struggling with a situation where I just cannot figure out how I'm supposed to do what I want with the borrow mechanism.

The code in its actual context can be found here: rlcs_predictor/main.rs at organize-data · CarlCochet/rlcs_predictor · GitHub and rlcs_predictor/region.rs at organize-data · CarlCochet/rlcs_predictor · GitHub

So here's the problem. I have a region which contains a bunch of teams and I am parsing a JSON dataset to find the result of a bunch of matches. So first, I need to find the teams, and once I have found them I need to simulate the match to update the ratings of both. This means the teams have to be mutable.

Currently in the code, it compiles because everything is immutable and so I'm not updating any rating:

let region = get_region(&regions, series.region.clone())?;
let blue_team = region.get_team(blue_ref.name.clone(), &blue_players)?;
let orange_team = region.get_team(orange_ref.name.clone(), &orange_players)?;
simulate_match(blue_team, orange_team);

Now the problem is, if I want to update the rating of the 2 teams, I need get_team() to return a mutable team, and if I do this I need region to be mutable as well. But then... I have 2 mutable references pointing on the same vector.

I've also tried to change to make a get_teams() to return both teams in a vec directly, but then I have the exact same problem, just inside the get_teams() function instead. I just cannot find a way to get 2 teams that belong to the same vector as mutables so that they can both be updated.

What kind of pattern would allow me to do this?

Supposing that you can modify the models, why don't you store the region within the Team model?

pub struct Team {
    pub name: String,
    pub players: Vec<Player>,
    pub rating: i32,
    pub region: Region
}

That way, you would have a Vec<Team> which is easier to work with, based on the needs that you described.

The best solution depends on the context. But there are several alternatives:

  • if you are sure you have distinct indices, then you can use split_at_mut() to get mutable references to different elements to the same slice, like this.
  • or you can return immutable references, cache the necessary modifications (e.g., scores), and then after the computation is done, release the immutable borrows, and mutably borrow the teams separately, performing the updates one-by-one.

Create a get_teams_mut method that looks up two teams and returns them. You can get multiple &mut into a slice or Vec with the split_*_mut methods.

Completely untested so it probably needs tweaking, but something like...

    // Let's say you've implemented `PartialEq<(&str, &[Player])>` for `Team`
    // for simpler code.  Consider a struct for (&str, &[Player]).
    pub fn get_teams_mut(
        &mut self, 
        name1: &str, 
        players1: &[Player]
        name2: &str,
        players2: &[Player],
    ) -> Option<(&mut Team, &mut Team)> {
        let idx1 = self.teams.iter().position(|t| t == (name1, players1))?;
        let (left, right) = self.teams.split_at_mut(idx1);
        let (team1, right) = right.split_first()?;
        if let Some(team2) = right.iter_mut().find(|t| t == (name2, players2)) {
            (team1, team2)
        } else {
            let team2 = left.iter_mut().find(|t| t == (name2, players2))?;
            (team2, team1)
        }
    }

Ah okay I wasn't aware about split_at_mut()! I assume I need to first find the position of both elements to know if I should invert or not the return, but this should work! Thanks, I'll try it out

Okay I think I see how that could work ; good point about implementing PartialEq<(&str, &[Player])> that would really clear up my code, thanks for the suggestion as well!

The dumb, general option, is to return an index instead of a ref for lookups, but you have to be careful if the collection itself can be reordered or otherwise invalidating those indices (if it can be, switching from a vec to a hashmap can be a simple solution, but there's specialized crates for better performance)

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.