Как вы можете использовать неизменяемую опцию по ссылке, которая содержит изменяемую ссылку? - PullRequest
0 голосов
/ 26 февраля 2019

Вот 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 теперь передается ссылкой mutable , а это not то, что я хочу:

  • Мне разрешено мутировать Option путем переназначения None, нарушая все последующие мутации.(Раскомментируйте строку 12 ((*handle) = None;).)почему я должен использовать ref mut в операторе if let, в то время как соглашение должно использовать &mut везде.
  • Это противоречит цели наличия сложных правил проверки заимствований и проверки изменчивости в компиляторе.

Есть ли способ на самом деле добиться того, что я хочу: передать неизменяемое Option, по ссылке, и реально использовать его содержимое?

Ответы [ 2 ]

0 голосов
/ 26 февраля 2019

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);
}

Примечания:

  1. Option<> содержит единственную существующую изменяемую ссылкуThing
  2. try_increment_twice() вступает во владение Option<>
  3. try_increment() должен принять Option<> как &mut, так что компиляторзнает, что он имеет единственную изменяемую ссылку на Option<> во время вызова
  4. Если компилятор знает, что 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, может изменять только его содержимое, а не сам дескриптор,( См. Playground )

К сожалению, даже это ничего не дает, потому что, если у вас есть изменяемая ссылка, вы всегда можете переназначить ее с помощью оператора разыменования:

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>

0 голосов
/ 26 февраля 2019

Вы не можете извлечь изменяемую ссылку из неизменной, даже ссылку на ее внутреннюю часть.Это своего рода точка!Допускается использование нескольких псевдонимов неизменяемых ссылок, поэтому, если Rust разрешил вам сделать это, вы можете столкнуться с ситуацией, когда два фрагмента кода могут одновременно изменять одни и те же данные.

Rust предоставляет несколько escape-люковдля изменчивость интерьера , например 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...