Entry :: or_insert выполняется несмотря на уже существующее значение - PullRequest
2 голосов
/ 07 февраля 2020

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

Попробуйте изменить Cacher так, чтобы он содержал карту ha sh, а не одно значение. Ключи карты ha sh будут передаваемыми значениями arg, а значения карты ha sh будут результатом вызова замыкания для этого ключа. Вместо того, чтобы посмотреть, имеет ли self.value непосредственное значение Some или None, функция value ищет аргумент на карте ha sh и возвращает значение, если оно присутствует. Если он отсутствует, Cacher вызовет замыкание и сохранит полученное значение в карте ha sh, связанной с его значением arg.

Вторая проблема с текущей реализацией Cacher заключается в том, что он принимает только замыкания, которые взять один параметр типа u32 и вернуть u32. Например, мы можем захотеть кэшировать результаты замыканий, которые берут фрагмент строки и возвращают значения usize. Чтобы решить эту проблему, попробуйте ввести более общие параметры c, чтобы повысить гибкость функции Cacher.

Для решения этого упражнения я использовал следующий код:

struct Cacher<T, K, V>
    where T: Fn(K) -> V
{
    calculation: T,
    values: HashMap<K, V>,
}

impl<T, K, V> Cacher<T, K, V>
    where T: Fn(K) -> V,
          K: std::cmp::Eq + std::hash::Hash + Clone,
{
    fn new(calculation: T) -> Cacher<T, K, V> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }

    fn value(&mut self, intensity: K) -> &V {
        self.values.entry(intensity.clone()).or_insert((self.calculation)(intensity))
    }
}

Этот код компилируется и выполняется, но не является правильным Cacher из-за того, что (self.calculation)(intensity) всегда выполняется. Даже когда запись существует. Из документации и примеров я понимаю, что функция Entry::or_insert выполняется только в том случае, если Entry не существует.

Мне известно о решении упражнения из вопроса Возможно ли это использовать один шаблон c для ключа и значения HashMap? , но я хотел бы знать, возможно ли решить проблему так, как я это делаю в настоящее время.

Редактировать : Как поясняется в комментарии: or_insert_with не решает проблему. При попытке or_insert_with(|| (self.calculation)(intensity.clone())) я получаю следующую ошибку error[E0502]: cannot borrow self as immutable because it is also borrowed as mutable.

1 Ответ

6 голосов
/ 07 февраля 2020

Проблема вашего кода в том, что аргументы функции всегда оцениваются перед вызовом функции в Rust (и в большинстве обязательных языков). Это означает, что перед вызовом or_insert() код безоговорочно вызовет (self.calculation)(intensity). Функция or_insert() будет внутренне проверять, присутствовало ли уже значение в записи, и вставлять только новое значение, переданное в качестве аргумента, если его нет, но это происходит только после того, как self.calculation уже было named.

Эту проблему можно решить с помощью метода or_insert_with(). Этот метод принимает замыкание вместо значения и вызывает замыкание, только если ему нужно вставить значение. Вот полный код:

use std::collections::HashMap;

struct Cacher<T, K, V> {
    calculation: T,
    values: HashMap<K, V>,
}

impl<T, K, V> Cacher<T, K, V>
where
    K: std::cmp::Eq + std::hash::Hash + Clone,
{
    fn new(calculation: T) -> Cacher<T, K, V> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }

    fn value(&mut self, intensity: K) -> &V
    where
        T: Fn(K) -> V,
    {
        let calculation = &self.calculation;
        self.values
            .entry(intensity.clone())
            .or_insert_with(|| calculation(intensity))
    }
}

Одна из особенностей реализации value() заключается в том, что вам необходимо сохранить ссылку на self.calculation в отдельной переменной. В противном случае закрытие вызовет заимствование self, которое перекрывается с изменяемым заимствованием self.values, вызванным вызовом self.values.entry(). Если вы явно заимствуете только self.calculation во внешней области видимости, то средство проверки заимствований достаточно умен, чтобы понять, что оно не перекрывается с self.values.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...