Я реализую игру жизни Конвея, чтобы научить себя Русту.Идея состоит в том, чтобы сначала реализовать однопотоковую версию, максимально оптимизировать ее, а затем сделать то же самое для многопоточной версии.
Я хотел реализовать альтернативную компоновку данных, которая, на мой взгляд, могла бы быть болеекэш удобно.Идея состоит в том, чтобы хранить состояние двух ячеек для каждой точки на плате рядом друг с другом в памяти в векторе, одну ячейку для считывания статуса текущего поколения и одну для записи статуса следующего поколения, чередуя схему доступа длявычисления каждого поколения (которые могут быть определены во время компиляции).
Основные структуры данных следующие:
#[repr(u8)]
pub enum CellStatus {
DEAD,
ALIVE,
}
/** 2 bytes */
pub struct CellRW(CellStatus, CellStatus);
pub struct TupleBoard {
width: usize,
height: usize,
cells: Vec<CellRW>,
}
/** used to keep track of current pos with iterator e.g. */
pub struct BoardPos {
x_pos: usize,
y_pos: usize,
offset: usize,
}
pub struct BoardEvo {
board: TupleBoard,
}
Функция, которая вызывает у меня проблемы:
impl BoardEvo {
fn evolve_step<T: RWSelector>(&mut self) {
for (pos, cell) in self.board.iter_mut() {
//pos: BoardPos, cell: &mut CellRW
let read: &CellStatus = T::read(cell); //chooses the right tuple half for the current evolution step
let write: &mut CellStatus = T::write(cell);
let alive_count = pos.neighbours::<T>(&self.board).iter() //<- can't borrow self.board again!
.filter(|&&status| status == CellStatus::ALIVE)
.count();
*write = CellStatus::evolve(*read, alive_count);
}
}
}
impl BoardPos {
/* ... */
pub fn neighbours<T: RWSelector>(&self, board: &BoardTuple) -> [CellStatus; 8] {
/* ... */
}
}
Черта RWSelector
имеет статические функции для чтения и записи в кортеж ячейки (CellRW
).Он реализован для двух типов нулевого размера L
и R
и в основном позволяет избежать необходимости писать разные методы для разных шаблонов доступа.
Метод iter_mut()
возвращает BoardIter
struct, которая является оберткой вокруг итератора изменяемого фрагмента для вектора ячеек и, таким образом, имеет тип &mut CellRW
как Item
.Он также знает о текущих BoardPos
(координаты x и y, смещение).
Я думал, что я буду перебирать все наборы ячеек, отслеживать координаты, подсчитывать количество живых соседей (Iнеобходимо знать координаты / смещения для этого) для каждой (считываемой) ячейки, вычислить состояние ячейки для следующего поколения и записать в соответствующую другую половину кортежа.
Конечно, в конце концов, компиляторпоказал мне роковой недостаток в моем дизайне, поскольку я заимствовал self.board
изменчиво в методе iter_mut()
, а затем снова пытался заимствовать его снова, чтобы получить всех соседей ячейки чтения.
Я не былв состоянии найти хорошее решение этой проблемы до сих пор.Мне удалось заставить его работать, сделав все ссылки неизменяемыми, а затем с помощью UnsafeCell
превратил неизменную ссылку на ячейку записи в изменяемую.Затем я пишу в условно неизменную ссылку на записывающую часть кортежа через UnsafeCell
.Тем не менее, это не поразило меня как звуковой дизайн, и я подозреваю, что у меня могут возникнуть проблемы с этим при попытке распараллеливания вещей.
Есть ли способ реализовать макет данных, который я предложил в safe / idiomatic Rustили это действительно тот случай, когда вам действительно приходится использовать приемы, чтобы обойти ограничения псевдонимов / заимствований в Rust?
Кроме того, существует ли более широкий вопрос о том, существует ли распознаваемый шаблон для проблем, которые требуют от вас обойти ограничения на заимствования в Rust?