rustborrow-checkerrefcell

Is RefCell an appropriate workaround to borrow two mutable elements from a vector?


Consider this toy example of "fighting" two random "players":

#[derive(Clone)]
struct Player {
    name: String,
    health: i32,
    attack: i32,
}

fn fight(player_a: &mut Player, player_b: &mut Player) {
    player_a.health -= player_b.attack;
    player_b.health -= player_a.attack;
}

fn main() {
    // Create Vector of 100 new players
    let players: Vec<Player> = vec![
        Player {
            name: String::new(),
            health: 100,
            attack: 5,
        };
        100
    ];

    // Pick two "random" indices
    let i1 = 19;
    let i2 = 30;

    fight(&mut players[i1], &mut players[i2]); // Error!
}

This code will not work as the fight function takes two mutable references to elements of the same players vector.

My ugly workaround currently looks like the following, using RefCell:

use std::cell::RefCell;

let mut players: Vec<RefCell<Player>> = vec![];
for _ in 0..100 {
    players.push(RefCell::new(Player {
        name: String::new(),
        health: 100,
        attack: 5,
    }));
}

fight(&mut players[i1].borrow_mut(), &mut players[i2].borrow_mut());

I'd like to know if there's a more efficient way of doing this to avoid the extra overhead of RefCell? Can I leverage split_at_mut somehow?


Solution

  • You can change fight method like next:

    #[derive(Clone)]
    struct Player {
        name: String,
        health: i32,
        attack: i32,
    }
    
    fn fight(players: &mut [Player], player1_index: usize, player2_index: usize) {
        players[player1_index].health -= players[player2_index].attack;
        players[player2_index].health -= players[player1_index].attack;
    }
    
    fn main() {
        // Create Vector of 100 new players
        let mut players: Vec<Player> = vec![
            Player {
                name: String::new(),
                health: 100,
                attack: 5,
            };
            100
        ];
    
        // Pick two "random" indices
        let i1 = 19;
        let i2 = 30;
    
        fight(&mut players, i1, i2);
    }
    

    Or you can try to workaround this issue with Option:

    #[derive(Clone)]
    struct Player {
        name: String,
        health: i32,
        attack: i32,
    }
    
    fn fight(player_a: &mut Player, player_b: &mut Player) {
        player_a.health -= player_b.attack;
        player_b.health -= player_a.attack;
    }
    
    fn main() {
        // Create Vector of 100 new players
        let mut players: Vec<Option<Player>> = vec![
            Some(Player {
                name: String::new(),
                health: 100,
                attack: 5,
            });
            100
        ];
    
        // Pick two "random" indices
        let i1 = 19;
        let i2 = 30;
    
        let mut player1 = players[i1].take().unwrap();
        let mut player2 = players[i2].take().unwrap();
        fight(&mut player1, &mut player2);
        players[i1].replace(player1);
        players[i2].replace(player2);
    }
    

    Or if you really need 100% performance, you can try to dig deeper into unsafe raw pointers. But you should think twice.