Entry :: Occupied.get () возвращает значение, ссылающееся на данные, принадлежащие текущей функции, даже если hashmap должен иметь владельца - PullRequest
3 голосов
/ 08 февраля 2020

Моя цель состояла в том, чтобы реализовать предложенное улучшение структуры кэширования главы 13.1 книги о ржавчине, которая заключается в создании структуры, которая принимает функцию и использует запоминание для уменьшения количества вызовов данной функции. , Чтобы сделать это, я создал структуру с HashMap

struct Cacher<T, U, V>
where T: Fn(&U) -> V, U: Eq + Hash
{
  calculation: T,
  map: HashMap<U,V>,
}

и двумя методами, одним конструктором и одним, который является ответственным за памятку.

impl<T, U, V> Cacher<T, U, V>
    where T: Fn(&U) -> V, U: Eq + Hash
{
    fn new(calculation: T) -> Cacher<T,U,V> {
        Cacher {
            calculation,
            map: HashMap::new(),
        }
    }

    fn value(&mut self, arg: U) -> &V {
        match self.map.entry(arg){
            Entry::Occupied(occEntry) => occEntry.get(),
            Entry::Vacant(vacEntry) => {
                let argRef = vacEntry.key();
                let result = (self.calculation)(argRef);
                vacEntry.insert(result)
            }
        }
    }
}

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

Если я хочу скомпилировать приведенный выше код, я получаю сообщение об ошибке, в котором говорится, что occEntry заимствовано его .get ( ) метод (который мне подходит) и что .get () "возвращает значение, ссылающееся на данные, принадлежащие текущей функции" .

My понимание состоит в том, что компилятор считает, что значение, на которое ссылается occEntry.get () , принадлежит функции value (...) . Но я не должен получить ссылку на значение типа V, который принадлежит HashMap ? Не запутался ли компилятор, поскольку значение принадлежит функции и на короткое время сохраняется как результат ?

let result = (self.calculation)(argRef);
vacEntry.insert(result)

Обратите внимание, что необходимо временно сохранить результат, так как insert метод потребляет ключ и такой argRef больше не действителен. Также я признаю, что подпись значение может быть проблематичной c (см. Изменяемый заимствование из HashMap и жизненного разрешения ), но я попытался избежать черты Copy Bound.

Для быстрого воспроизведения проблемы я добавляю use операторов обязательно. Спасибо за вашу помощь.

use std::collections::HashMap;
use std::cmp::Eq;
use std::hash::Hash;
use std::collections::hash_map::{OccupiedEntry, VacantEntry, Entry};

1 Ответ

3 голосов
/ 08 февраля 2020

Давайте посмотрим на подпись OccupiedEntry::get():

 pub fn get(&self) -> &V

Что эта подпись говорит нам о том, что ссылка, полученная из OccupiedEntry, может жить только как пока сама OccupiedEntry. Тем не менее, OccupiedEntry является локальной переменной, поэтому она удаляется при возврате функции.

Нам нужна ссылка, время жизни которой связано с временем жизни HashMap. И Entry, и OccupiedEntry имеют параметр времени жизни ('a), который связан с параметром &mut self в HashMap::entry. Нам нужен метод на OccupiedEntry, который возвращает &'a V. Нет такого метода, но есть метод, который возвращает '&a mut V: into_mut. Изменяемая ссылка может быть неявно приведена к общей ссылке, поэтому все, что нам нужно сделать для компиляции вашего метода, это заменить get() на into_mut().

fn value(&mut self, arg: U) -> &V {
    match self.map.entry(arg) {
        Entry::Occupied(occ_entry) => occ_entry.into_mut(),
        Entry::Vacant(vac_entry) => {
            let arg_ref = vac_entry.key();
            let result = (self.calculation)(arg_ref);
            vac_entry.insert(result)
        }
    }
}
...