Я реализую кеш, который пытается выполнить поиск в таблице, если это не удается, он пытается получить значение простым методом, а если это не удается, он идет и вычисляет несколько новых записей в кеше. Система Entry кажется специально разработанной для первой половины этого, но я не могу заставить средство проверки заимствования позволить мне завершить вторую половину.
use std::collections::HashMap;
fn main() {
let mut cache = Cache { cache: HashMap::new() };
println!("{}", cache.get_from_cache(10));
}
struct Cache {
cache: HashMap<u32, String>
}
impl Cache {
fn get_from_cache<'a>(&'a mut self, i: u32) -> &'a String {
match self.cache.entry(i) {
std::collections::hash_map::Entry::Occupied(entry) => return entry.into_mut(),
std::collections::hash_map::Entry::Vacant(entry) => {
// Some values have an easy way to be computed...
if i == 5 {
return entry.insert("my string".to_string())
}
}
}
// Neither look-up method succeeded, so we 'compute' values one-by-one
for j in 1..=i {
self.cache.insert(j, "another string".to_string()); // Borrow checker fails here
}
self.cache.get(&i).unwrap()
}
}
Проблема в том, что Entry у self.cache.entry(i) заимствует self.cache на всю жизнь 'a, хотя мне это больше не нужно в тот момент, когда я пытаюсь это сделать self.cache.insert.
Одним из решений этого (я думаю) было бы, если бы был способ превратить мою ссылку на Entry в ссылку на его HashMap, а затем вставить через эту ссылку. Однако я не вижу возможности сделать это с интерфейсом entry. Есть ли способ добиться этого? Или иным образом удовлетворить проверку заимствования?

Это легко исправить, отделив вставку значений от возврата конечного результата. Вы можете сначала убедиться, что значение находится в кеше или вставить его с помощью какой-либо стратегии, если нет, а затем вернуть новое значение (теперь оно гарантированно будет в HashMap):
fn get_from_cache<'a>(&'a mut self, i: u32) -> &'a String {
// handle inserting the value if necessary:
match self.cache.entry(i) {
std::collections::hash_map::Entry::Occupied(entry) => (),
// Some values have an easy way to be computed...
std::collections::hash_map::Entry::Vacant(entry) if i == 5 => {
entry.insert("my string".to_string());
}
// ... others aren't
std::collections::hash_map::Entry::Vacant(entry) => {
for j in 1..=i {
self.cache.insert(j, "another string".to_string());
}
}
}
// The value is now definitely in `self.cache` so we can return a reference to it
&self.cache[&i]
}
&self.cache[&i] по сути такой же, как self.cache.get(&i).unwrap(), и имеет избыточную проверку, которая может быть оптимизирована, а может и нет.
@PitaJ Правда? Глядя на исходный код, кажется, что index просто вызывает self.get(key).expect(...).
expect — это просто unwrap с указанным сообщением.
Да, так что я не понимаю, в чем должна быть разница между &self.cache[&i] и self.cache.get(&i).unwrap()?
Я думаю, произошло недоразумение. Моя точка зрения заключалась именно в том, что они одинаковы и что ОБА вводят избыточную проверку наличия записи под &i. В этой избыточной проверке не было бы необходимости, если бы средство проверки заимствования не было ограничено.
Ах, извините, теперь я понимаю, что вы имеете в виду. Я не имел в виду, что индексирование не будет выполнять проверку, которую делает get (я просто подумал, что это выглядит чище), но я понимаю, почему вы так думаете. Да, очевидно, было бы неплохо просто вернуть ссылку, когда мы уже проверили, что там есть значение. Когда-нибудь, Полоний, когда-нибудь...
Так почему же entry все еще не имеет изменяемой ссылки на self.cache в момент выполнения self.cache.insert в этом примере? В этом случае NLL смог сделать вывод, что после этого запись не использовалась? Разочаровывает то, что решение в основном «не используйте entry», хотя Entry было создано для проблем, очень похожих на эту.
entryAPI существует, чтобы избежать подобных проблем, вызванных ограничениями средства проверки заимствования. Я думаю, вам придется использовать избыточную проверкуcontains_keyв качестве обходного пути на данный момент.