【问题标题】:Is RefCell an appropriate workaround to borrow two mutable elements from a vector?RefCell 是从向量中借用两个可变元素的适当解决方法吗?
【发布时间】:2021-06-30 23:23:18
【问题描述】:

考虑这个“战斗”两个随机“玩家”的玩具示例:

#[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!
}

此代码将不起作用,因为 fight 函数需要对同一 players 向量的元素进行两个可变引用。

我的丑陋解决方法目前如下所示,使用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());

我想知道是否有更有效的方法来避免RefCell 的额外开销?我可以以某种方式利用split_at_mut 吗?

【问题讨论】:

    标签: rust borrow-checker refcell


    【解决方案1】:

    可以使用split_at_mut 专门借用两者:

    #[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 get2<T>(arr: &mut [T], a: usize, b: usize) -> (&mut T, &mut T) {
        use std::cmp::Ordering;
    
        let (sw, a, b) = match Ord::cmp(&a, &b) {
            Ordering::Less => (false, a, b),
            Ordering::Greater => (true, b, a),
            Ordering::Equal =>
                panic!("attempted to exclusive-borrow one element twice"),
        };
        
        let (arr0, arr1) = arr.split_at_mut(a + 1);
        let (ea, eb) = (&mut arr0[a], &mut arr1[b - a + 1]);
    
        if sw {
            (eb, ea)
        } else {
            (ea, eb)
        }
    }
    
    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;
        
        let (p1, p2) = get2(&mut players, i1, i2);
    
        println!("{} ({} HP) vs {} ({} HP)",
            p1.attack, p1.health, p2.attack, p2.health);
        fight(p1, p2);
        println!("{} ({} HP) vs {} ({} HP)",
            p1.attack, p1.health, p2.attack, p2.health);
    }
    

    【讨论】:

      【解决方案2】:

      您可以更改fight 方法,如下所示:

      #[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);
      }
      

      或者您可以尝试使用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);
      }
      

      或者,如果您真的需要 100% 的性能,您可以尝试深入挖掘不安全的原始指针。但你应该三思而后行。

      【讨论】:

      • 我已经进行了一些基准测试,到目前为止,所有 3 种提议的解决方案都比原来的 RefCell 解决方法快两倍左右。您修改 fight 函数的第一个建议是最快和最简单的,并且看起来像要走的路。谢谢!
      猜你喜欢
      • 2021-05-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-24
      • 2022-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多