Я реализую кеш, который пытается выполнить поиск в таблице, если это не удается, он пытается получить значение простым методом, а если это не удается, он идет и вычисляет несколько новых записей в кеше. Система 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
было создано для проблем, очень похожих на эту.
entry
API существует, чтобы избежать подобных проблем, вызванных ограничениями средства проверки заимствования. Я думаю, вам придется использовать избыточную проверкуcontains_key
в качестве обходного пути на данный момент.