📘 Условие
Ты хочешь реализовать простой кэш — если значение уже вычислено, вернуть его, иначе — сохранить и вернуть.
Вот пример:
use std::collections::HashMap;
fn main() {
let mut cache = HashMap::new();
let key = "user_123".to_string();
let result = get_or_insert(&mut cache, &key, || {
println!("Computing...");
"result for user_123".to_string()
});
println!("Result: {}", result);
}
fn get_or_insert<'a, F>(map: &'a mut HashMap<String, String>, key: &str, compute: F) -> &'a String
where
F: FnOnce() -> String,
{
map.entry(key.to_string()).or_insert_with(compute)
}
❓ Вопрос:
1) Почему этот код не компилируется, хотя кажется безопасным?
2) Где именно проблема с lifetime'ами?
3) Как можно переписать этот код так, чтобы он компилировался и оставался эффективным?
---
✅ Подвох и разбор
💥 Проблема в
or_insert_with(compute) и владении ключом.Метод
.entry() требует ключ во владение (`String`), а key у нас — &str. Внутри
or_insert_with происходит вызов compute(), который может вернуть ссылку на строку, но Rust не может доказать, что ссылка будет жить достаточно долго.Но главная причина — возвращаемое значение
&'a String, полученное из HashMap::entry, не может быть безопасно связано с временем жизни `map`, потому что key.to_string() создаёт временное значение, и lifetime не совместим.📌 Ошибка компилятора: borrow may not live long enough.
✅ Как исправить
Вариант 1 — использовать `Entry` напрямую и разбить на шаги:
```rust
fn get_or_insert<'a, F>(map: &'a mut HashMap<String, String>, key: &str, compute: F) -> &'a String
where
F: FnOnce() -> String,
{
use std::collections::hash_map::Entry;
match map.entry(key.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => entry.insert(compute()),
}
}
```
Теперь Rust понимает, как обрабатывается владение, и может гарантировать корректный lifetime.
---
⚠️ Подвох
• `.or_insert_with(...)` выглядит безопасным, но может скрывать временные владения
• Проблемы начинаются, когда `key.to_string()` создаёт временное значение, и Rust не может связать его lifetime
• Даже опытные разработчики удивляются ошибке компилятора, потому что
map.entry().or_insert_with() выглядит «канонично»🎯 Отлично подходит для проверки глубокого понимания владения и жизненных циклов в Rust.