Как исправить .. было взаимно позаимствовано здесь в предыдущей итерации цикла в Rust?

Мне нужно перебрать ключи, найти значение в HashMap по ключу, возможно, выполнить тяжелые вычисления в найденной структуре в качестве значения (lazy = ›mutate the struct) и вернуть его в кэш в Rust.

Я получаю следующее сообщение об ошибке:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:25:26
   |
23 |     fn it(&mut self) -> Option<&Box<Calculation>> {
   |           - let's call the lifetime of this reference `'1`
24 |         for key in vec!["1","2","3"] {
25 |             let result = self.find(&key.to_owned());
   |                          ^^^^ `*self` was mutably borrowed here in the previous iteration of the loop
...
28 |                 return result
   |                        ------ returning this value requires that `*self` is borrowed for `'1`

Вот код на игровой площадке.

use std::collections::HashMap;

struct Calculation {
    value: Option<i32>
}

struct Struct {
    items: HashMap<String, Box<Calculation>> // cache
}

impl Struct {
    fn find(&mut self, key: &String) -> Option<&Box<Calculation>> {
        None // find, create, and/or calculate items
    }

    fn it(&mut self) -> Option<&Box<Calculation>> {
        for key in vec!["1","2","3"] {
            let result = self.find(&key.to_owned());
            if result.is_some() {
                return result
            }
        }
        None
    }
}
  • Я не могу избежать цикла, так как мне нужно проверить несколько ключей
  • Я должен сделать его изменяемым (self и структура), так как возможные вычисления меняют его.

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

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


person 4ntoine    schedule 12.02.2021    source источник
comment
Удивительно, но многие люди проводят время, задавая длинный вопрос о переполнении стека, не читая сначала сообщения об ошибке, но, похоже, это не один из таких случаев. Действительно, не совсем понятно, как решить эту проблему. Код безопасен, но программа проверки заимствований недостаточно умен, чтобы в нем разобраться.   -  person Sven Marnach    schedule 12.02.2021
comment
Я отредактировал вопрос, включив в него полное сообщение об ошибке, и отменил свой голос против.   -  person trentcl    schedule 12.02.2021


Ответы (2)


Вы не можете выполнять кэширование с эксклюзивным доступом. Вы не можете рассматривать ссылки Rust как указатели общего назначения (BTW: &String и &Box<T> - это двойная косвенная адресация и очень унидиоматичны в Rust. Используйте &str или &T для временных заимствований).

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

Вам нужно убедить контролера займов, что вещь, которую возвращает find, не исчезнет внезапно в следующий раз, когда вы ее вызовете. В настоящее время такой гарантии нет, потому что интерфейс не мешает вам звонить, например. items.clear() (программа проверки заимствований проверяет, что позволяет интерфейс функции, а не то, что функция на самом деле делает).

Это можно сделать либо с помощью Rc, либо с помощью ящика, в котором реализован пул / арена памяти.

struct Struct {
   items: HashMap<String, Rc<Calculation>>,
}

fn find(&mut self, key: &str) -> Rc<Calculation> 

Таким образом, если вы клонируете Rc, он будет жить столько, сколько ему нужно, независимо от кеша.

Вы также можете сделать его лучше с помощью внутренней изменчивости.

struct Struct {
   items: RefCell<HashMap<…
}

Это позволит вашему мемоизирующему find методу использовать общий заим вместо эксклюзивного:

fn find(&self, key: &str) -> …

с которым гораздо проще работать вызывающим метод.

person Kornel    schedule 12.02.2021
comment
Чтобы изменить Calculation (Rc::get_mut()) Rc ‹Calculation›, strong_count должен быть = 0, но его можно где-то использовать. По сути, это блокирует мутацию до тех пор, пока она не будет прочитана или где-то сохранена. Поэтому простое использование RC не помогает, если я правильно понимаю (вместо проблемы с компиляцией у нас такая же проблема во время выполнения) - person 4ntoine; 15.02.2021
comment
Если вам нужна общая изменчивость (что приводит к изменению и кешированных записей), используйте Rc<RefCell<Calculation>>. - person Kornel; 18.02.2021

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

impl Struct {

    fn find(&mut self, key: &String) -> Option<&Box<Calculation>> {
        None
    }

    fn it(&mut self) -> Option<&Box<Calculation>> {
        for key in vec!["1","2","3"] {
            if self.find(&key.to_owned()).is_some() {
                return self.find(&key.to_owned());
            }
        }
        None
    }
}
person Bromind    schedule 12.02.2021
comment
это не очень хорошо сказывается на производительности: find вызывается дважды. Должен быть правильный способ - person 4ntoine; 12.02.2021
comment
Согласен с тобой, могло быть и лучше. С другой стороны, для вашего конкретного случая find не так дорого, поскольку это только поиск в хеш-таблице, и вы знаете, что значение существует. Если я правильно понимаю ваши намерения, тяжелый расчет все равно выполняется один раз, так что, возможно, этот двойной вызов довольно мал по сравнению с этим. Я постараюсь придумать лучший способ сделать это. - person Bromind; 12.02.2021