Как вы можете использовать неизменяемую опцию по ссылке, которая содержит изменяемую ссылку?
Вот Thing
:
struct Thing(i32);
impl Thing {
pub fn increment_self(&mut self) {
self.0 += 1;
println!("incremented: {}", self.0);
}
}
А вот функция, которая пытается изменить Thing
и возвращает либо true, либо false, в зависимости от того, Thing
доступен:
fn try_increment(handle: Option<&mut Thing>) -> bool {
if let Some(t) = handle {
t.increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
Вот пример использования:
fn main() {
try_increment(None);
let mut thing = Thing(0);
try_increment(Some(&mut thing));
try_increment(Some(&mut thing));
try_increment(None);
}
Как написано выше, все работает просто отлично (ссылка на площадку Rust). Выход ниже:
warning: increment failed
incremented: 1
incremented: 2
warning: increment failed
Проблема возникает, когда я хочу написать функцию, которая изменяет Thing
дважды. Например, следующее не работает:
fn try_increment_twice(handle: Option<&mut Thing>) {
try_increment(handle);
try_increment(handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
Ошибка имеет смысл. Первый звонок try_increment(handle)
дает право собственности на handle
прочь и поэтому второй звонок незаконен. Как это часто бывает, компилятор Rust выдает разумное сообщение об ошибке:
|
24 | try_increment(handle);
| ------ value moved here
25 | try_increment(handle);
| ^^^^^^ value used here after move
|
В попытке решить эту проблему, я подумал, что имеет смысл передать handle
по ссылке. Это должна быть незыблемая ссылка, ум, потому что я не хочу try_increment
быть в состоянии изменить handle
сам (присваивая None
к нему, например), только чтобы иметь возможность вызывать мутации по его значению.
Моя проблема в том, что я не мог понять, как это сделать.
Вот самая близкая рабочая версия, которую я мог получить:
struct Thing(i32);
impl Thing {
pub fn increment_self(&mut self) {
self.0 += 1;
println!("incremented: {}", self.0);
}
}
fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
// PROBLEM: this line is allowed!
// (*handle) = None;
if let Some(ref mut t) = handle {
t.increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(mut handle: Option<&mut Thing>) {
try_increment(&mut handle);
try_increment(&mut handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
Этот код работает, как и ожидалось, но Option
теперь передается по изменяемой ссылке, и это не то, что я хочу:
- Мне разрешено мутировать
Option
переназначаяNone
к нему, нарушая все последующие мутации. (Раскомментируйте строку 12 ((*handle) = None;
) например.) - Это грязно: есть много посторонних
&mut
врёт. - Это вдвойне грязно: Небеса знают только, почему я должен использовать
ref mut
вif let
заявление в то время как соглашение заключается в использовании&mut
где-либо еще. - Это лишает цели наличия сложных правил проверки заимствований и проверки изменчивости в компиляторе.
Есть ли способ на самом деле достичь того, что я хочу: передать неизменное Option
где, по ссылке, и на самом деле в состоянии использовать его содержимое?
2 ответа
TL;DR: ответ - нет, я не могу.
После обсуждения с @Peter Hall и @Stargateur я понял, почему мне нужно использовать &mut Option<&mut Thing>
везде. RefCell<>
также было бы возможным обходным путем, но он не аккуратен и на самом деле не соответствует шаблону, который я изначально стремился реализовать.
Проблема заключается в следующем: если разрешить видоизменять объект, для которого имеется только неизменная ссылка на Option<&mut T>
можно использовать эту власть, чтобы полностью нарушить правила заимствования. Конкретно, у вас может быть много изменяемых ссылок на один и тот же объект, потому что у вас может быть много таких неизменных ссылок.
Я знал, что была только одна изменчивая ссылка на Thing
(принадлежит Option<>
) но, как только я начал брать ссылки на Option<>
компилятор больше не знал, что их не так много.
Лучший вариант шаблона выглядит следующим образом:
fn try_increment(handle: &mut Option<&mut Thing>) -> bool {
if let Some(ref mut t) = handle {
t.increment_self();
true
}
else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(mut handle: Option<&mut Thing>) {
try_increment(&mut handle);
try_increment(&mut handle);
}
fn main() {
try_increment_twice(None);
let mut thing = Thing(0);
try_increment_twice(Some(&mut thing));
try_increment_twice(None);
}
Заметки:
Option<>
содержит единственную существующую изменчивую ссылку наThing
try_increment_twice()
вступает во владениеOption<>
try_increment()
должен взятьOption<>
как&mut
так что компилятор знает, что он имеет единственную изменяемую ссылку наOption<>
во время разговора- Если компилятор знает, что
try_increment()
имеет единственную изменчивую ссылку наOption<>
который содержит уникальную изменяемую ссылку наThing
Компилятор знает, что правила заимствования не были нарушены.
Еще один эксперимент
Проблема изменчивости Option<>
остается, потому что можно позвонить take()
и другие. на изменчивом Option<>
нарушая все следующее.
Чтобы реализовать шаблон, который я хотел, мне нужно что-то вроде Option<>
но, даже если он изменчив, он не может быть видоизменен. Что-то вроде этого:
struct Handle<'a> {
value: Option<&'a mut Thing>,
}
impl<'a> Handle<'a> {
fn new(value: &'a mut Thing) -> Self {
Self {
value: Some(value),
}
}
fn empty() -> Self {
Self {
value: None,
}
}
fn try_mutate<T, F: Fn(&mut Thing) -> T>(&mut self, mutation: F) -> Option<T> {
if let Some(ref mut v) = self.value {
Some(mutation(v))
}
else {
None
}
}
}
Теперь я подумал, что могу обойти &mut Handle
целый день и знаю, что тот, кто имеет Handle
может изменять только его содержимое, а не сам дескриптор. ( См. Детская площадка)
К сожалению, даже это ничего не дает, потому что, если у вас есть изменяемая ссылка, вы всегда можете переназначить ее с помощью оператора разыменования:
fn try_increment(handle: &mut Handle) -> bool {
if let Some(_) = handle.try_mutate(|t| { t.increment_self() }) {
// This breaks future calls:
(*handle) = Handle::empty();
true
}
else {
println!("warning: increment failed");
false
}
}
Что все хорошо и хорошо.
Итог: просто используйте &mut Option<&mut T>
Вы не можете извлечь изменяемую ссылку из неизменной, даже ссылку на ее внутреннюю часть. Это своего рода точка! Допускается использование нескольких псевдонимов неизменяемых ссылок, поэтому, если Rust разрешил вам сделать это, вы можете столкнуться с ситуацией, когда два фрагмента кода могут одновременно изменять данные.
Rust предоставляет несколько аварийных люков для изменчивости интерьера, например RefCell
:
use std::cell::RefCell;
fn try_increment(handle: &Option<RefCell<Thing>>) -> bool {
if let Some(t) = handle {
t.borrow_mut().increment_self();
true
} else {
println!("warning: increment failed");
false
}
}
fn try_increment_twice(handle: Option<RefCell<Thing>>) {
try_increment(&handle);
try_increment(&handle);
}
fn main() {
let mut thing = RefCell::new(Thing(0));
try_increment_twice(Some(thing));
try_increment_twice(None);
}