Является ли возвращение ссылки на вариант перечисления в Rust хорошей идеей?

Я делаю игру «Крестики-нолики» на Rust в качестве проекта для начинающих и работал над расчетом состояния игры (выигрыш, ничья и т. д.), когда столкнулся с этой проблемой. Сначала я написал следующий код:

fn status(&self) -> Status {
    if Board::has_won(self.x_board) {
        Status::Won(true)
    } else if Board::has_won(self.o_board) {
        Status::Won(false)
    } else if self.is_full() {
        Status::Draw
    } else {
        Status::None
    }
}

Затем я изменил его, чтобы вернуть ссылку:

fn status(&self) -> &Status {
    if Board::has_won(self.x_board) {
        &Status::Won(true)
    } else if Board::has_won(self.o_board) {
        &Status::Won(false)
    } else if self.is_full() {
        &Status::Draw
    } else {
        &Status::None
    }
}

Я сделал это, чтобы связать вместе время жизни self и возвращаемое значение, чтобы, если состояние доски изменилось, ссылка на статус больше не могла использоваться.

Затем я попробовал этот код:

fn status(&self) -> &Status {
    let status;
    if Board::has_won(self.x_board) {
        status = Status::Won(true)
    } else if Board::has_won(self.o_board) {
        status = Status::Won(false)
    } else if self.is_full() {
        status = Status::Draw
    } else {
        status = Status::None
    }

    &status
}

И это привело к ошибке компилятора, говорящей, что он не может вернуть ссылку на собственные данные. Почему в первом фрагменте нет этой проблемы?

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
4
0
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я предполагаю, что первый фрагмент подлежит продлению времени жизни, а второй является ссылкой на явную локальную переменную и, следовательно, уже имеет явное время жизни. Правила продления срока службы сложны, поэтому я позволю кому-то другому обсудить их особенности. Вместо этого я хочу создать немного другой дизайн.

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

В общем, у вас есть Status. Это ценность, она не заимствована, и притворяться, что она заимствована, немного неловко. Это не неправильно, просто неудобно. Но вы хотите, чтобы он семантически вел себя так, как будто он заимствован, даже если это не так. Для этого мы можем использовать PhantomData.

Учти это. Оставьте перечисление Status в покое. Сделайте это Copy и Clone, так как это красиво и просто, и никогда не берите на это ссылки. Его все еще можно разумно использовать как независимую структуру данных, при условии, что вы никогда не предполагаете, что он привязан к какой-либо конкретной плате.

Теперь, когда вы хотите привязать его к определенной доске, используйте новую структуру, которую я назову BoardStatus.

struct BoardStatus<'a> {
  status: Status,
  _phantom: PhantomData<&'a Status>,
}

BoardStatus на самом деле просто Status. Его единственное поле ненулевого размера — это Status, поэтому их представления должны быть идентичными. Но у него также есть PhantomData. PhantomData<T> — это тип нулевого размера (это означает, что он не занимает места во время выполнения), который притворяется, что содержит T для жизненных целей. Итак, BoardStatus — это Status (собственное значение статуса, никаких заимствований), которое притворяется заимствованным на время 'a. Тогда ваш метод status может иметь этот возвращаемый тип.

fn<'a> status(&'a self) -> BoardStatus<'a>

или, с пожизненной элизией

fn status(&self) -> BoardStatus<'_>

Таким образом, есть разница между перечислением Status, которое можно тестировать независимо и которое не имеет дополнительного багажа; и структура BoardStatus, которая по-прежнему является просто статусом, но явно привязана к доске.

Самое приятное то, что все это имеет нулевые накладные расходы. Во время выполнения нет фактического указателя, поэтому BoardStatus точно так же эффективен, как Status во время выполнения, без косвенного обращения. Абстракция с нулевой стоимостью.

Кто-то еще сказал, что первый фрагмент может быть связан с вариантами, имеющими время жизни 'static. Спасибо за предложение PhantomData!

BlueZeeKing 11.02.2023 06:07

Я думаю, что первый эталонный пример работает не из-за продления жизни, а из-за статического продвижения rvalue, похожее явление, но не совсем то же самое.

rodrigo 11.02.2023 14:59

Другие вопросы по теме