Я делаю игру «Крестики-нолики» на 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
}
И это привело к ошибке компилятора, говорящей, что он не может вернуть ссылку на собственные данные. Почему в первом фрагменте нет этой проблемы?
Я предполагаю, что первый фрагмент подлежит продлению времени жизни, а второй является ссылкой на явную локальную переменную и, следовательно, уже имеет явное время жизни. Правила продления срока службы сложны, поэтому я позволю кому-то другому обсудить их особенности. Вместо этого я хочу создать немного другой дизайн.
То, что ты делаешь, достойно восхищения. На самом деле, это потрясающе. Я никогда не думал о такой привязке переменной состояния к структуре данных со ссылками. При этом вы немного лжете 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
во время выполнения, без косвенного обращения. Абстракция с нулевой стоимостью.
Я думаю, что первый эталонный пример работает не из-за продления жизни, а из-за статического продвижения rvalue, похожее явление, но не совсем то же самое.
Кто-то еще сказал, что первый фрагмент может быть связан с вариантами, имеющими время жизни
'static
. Спасибо за предложениеPhantomData
!