RefCell 是从向量中借用两个可变元素的适当解决方法吗?
Is RefCell an appropriate workaround to borrow two mutable elements from a vector?
考虑这个“打架”两个随机“玩家”的玩具示例:
#[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
吗?
您可以像下面那样更改 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% 的性能,你可以尝试更深入地挖掘不安全的原始指针。但是你要三思。
可以使用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);
}
考虑这个“打架”两个随机“玩家”的玩具示例:
#[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
吗?
您可以像下面那样更改 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% 的性能,你可以尝试更深入地挖掘不安全的原始指针。但是你要三思。
可以使用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);
}