Когда необходимо обойти проверку Rust заемщика? - PullRequest
0 голосов
/ 21 мая 2018

Я реализую игру жизни Конвея, чтобы научить себя Русту.Идея состоит в том, чтобы сначала реализовать однопотоковую версию, максимально оптимизировать ее, а затем сделать то же самое для многопоточной версии.

Я хотел реализовать альтернативную компоновку данных, которая, на мой взгляд, могла бы быть болеекэш удобно.Идея состоит в том, чтобы хранить состояние двух ячеек для каждой точки на плате рядом друг с другом в памяти в векторе, одну ячейку для считывания статуса текущего поколения и одну для записи статуса следующего поколения, чередуя схему доступа длявычисления каждого поколения (которые могут быть определены во время компиляции).

Основные структуры данных следующие:

#[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() возвращает BoardIterstruct, которая является оберткой вокруг итератора изменяемого фрагмента для вектора ячеек и, таким образом, имеет тип &mut CellRW как Item.Он также знает о текущих BoardPos (координаты x и y, смещение).

Я думал, что я буду перебирать все наборы ячеек, отслеживать координаты, подсчитывать количество живых соседей (Iнеобходимо знать координаты / смещения для этого) для каждой (считываемой) ячейки, вычислить состояние ячейки для следующего поколения и записать в соответствующую другую половину кортежа.

Конечно, в конце концов, компиляторпоказал мне роковой недостаток в моем дизайне, поскольку я заимствовал self.board изменчиво в методе iter_mut(), а затем снова пытался заимствовать его снова, чтобы получить всех соседей ячейки чтения.

Я не былв состоянии найти хорошее решение этой проблемы до сих пор.Мне удалось заставить его работать, сделав все ссылки неизменяемыми, а затем с помощью UnsafeCell превратил неизменную ссылку на ячейку записи в изменяемую.Затем я пишу в условно неизменную ссылку на записывающую часть кортежа через UnsafeCell.Тем не менее, это не поразило меня как звуковой дизайн, и я подозреваю, что у меня могут возникнуть проблемы с этим при попытке распараллеливания вещей.

Есть ли способ реализовать макет данных, который я предложил в safe / idiomatic Rustили это действительно тот случай, когда вам действительно приходится использовать приемы, чтобы обойти ограничения псевдонимов / заимствований в Rust?

Кроме того, существует ли более широкий вопрос о том, существует ли распознаваемый шаблон для проблем, которые требуют от вас обойти ограничения на заимствования в Rust?

1 Ответ

0 голосов
/ 21 мая 2018

Когда необходимо обойти средство проверки заимствований в Rust?

Это необходимо, когда:

  • средство проверки заимствований недостаточно развито, чтобы увидеть, что вашиспользование безопасно
  • вы не хотите (или не можете) писать код в другом шаблоне

В конкретном случае компилятор не может сказать, что это безопасно:

let mut array = [1, 2];
let a = &mut array[0];
let b = &mut array[1];

Компилятор не знает, что делает реализация IndexMut для среза в этой точке компиляции (это преднамеренный выбор дизайна).Насколько он знает, массивы всегда возвращают одну и ту же ссылку независимо от аргумента индекса. Мы можем сказать, что этот код безопасен, но компилятор запрещает его.

Вы можете переписать это так, что это очевидно безопасно для компилятора:

let mut array = [1, 2];
let (a, b) = array.split_at_mut(1);
let a = &mut a[0];
let b = &mut b[0];

Как это сделать?split_at_mut выполняет проверку во время выполнения , чтобы убедиться, что на самом деле является безопасным:

fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) {
    let len = self.len();
    let ptr = self.as_mut_ptr();

    unsafe {
        assert!(mid <= len);

        (from_raw_parts_mut(ptr, mid),
         from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
    }
}

Для примера, когда средство проверки заимствования еще не настолько продвинутый, насколько это возможно, см. Что такое нелексические времена жизни? .

Я беру self.board изменчиво в методе iter_mut(), а затем пытаюсь заимствоватьЭто опять-таки непреложно получить всех соседей ячейки чтения.

Если вы знаете, что ссылки не перекрываются, то вы можете использовать небезопасный код для его выражения.Однако это означает, что вы также решаете взять на себя ответственность за поддержание всех инвариантов Rust и избегать неопределенного поведения .

Хорошая новость заключается в том, что это тяжелое бремя - то, что каждый программист на C и C ++ должен (или, по крайней мере, должен ) иметь на своих плечах каждую строку кода, которую они пишут..По крайней мере, в Rust вы можете позволить компилятору работать с 99% случаев.

Во многих случаях есть такие инструменты, как Cell и RefCellчтобы учесть внутреннюю мутацию.В других случаях вы можете переписать свой алгоритм, чтобы использовать значение типа Copy.В других случаях вы можете использовать индекс в срез на более короткий период.В других случаях вы можете использовать многофазный алгоритм.

Если вам нужно прибегнуть к коду unsafe, постарайтесь скрыть его в небольшой области и открыть безопасные интерфейсы.

Прежде всего, много (более) часто задавались вопросы о:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...