Я хочу сделать структуру данных, которая хранит в себе элементы идентифицируемые ключом (для простоты примера здесь в качестве ключа выступает число). При этом я хочу, чтобы обращение к несуществующему элементу создавало его с нужным ключом, а с разными элементами можно было работать параллельно из разных потоков, при этом используя RAII (блокирующе получаем обёртку для работы с элементом, потому что HashMap однопоточный, к тому же процесс создания несуществующего элемента это потенциальный race condition, а затем через обёртку можем работать с нашим элементом уже вне зависимости от поведения других потоков, при этом после поиска/создания должен быть заблокирован уже только один конкретный элемент).
Максимально наивная реализация:
use std::sync::{Mutex, MutexGuard};
use std::collections::HashMap;
use std::collections::hash_map::Entry;
struct Item {
a: i32,
b: i32
}
struct ItemStorage {
items: Mutex<HashMap<i32, Box<Mutex<Item>>>>
}
struct ItemRef<'a> {
item: MutexGuard<'a, Item>
}
// Всякие new и прочие идеоматические вещи опущены для простоты
impl ItemStorage {
pub fn item(&mut self, key: i32) -> ItemRef {
let mut items_lock = self.items.lock().unwrap();
let item = match items_lock.entry(key) {
Entry::Occupied(v) => v.into_mut(),
Entry::Vacant(v) => {
v.insert(Box::new(Mutex::new(Item {
a: 0, b: 1
})))
}
};
ItemRef {
item: item.lock().unwrap()
}
}
}
fn main() {
let mut storage = ItemStorage {
items: Mutex::new(HashMap::new())
};
// Код ниже можно запускать из разных потоков как с одинаковыми, так и с разными ключами
let mut item_ref = storage.item(100);
item_ref.item.a += 1;
}
Ожидаемо Rust чувствует, что здесь что-то не так:
error[E0515]: cannot return value referencing local variable `items_lock`
--> src/main.rs:29:3
|
22 | let item = match items_lock.entry(key) {
| ---------- `items_lock` is borrowed here
...
30 | / ItemRef {
31 | | item: item.lock().unwrap()
32 | | }
| |_________^ returns a value referencing data owned by the current function
error: aborting due to previous error
Как обрабатываются правильно такие ситуации? Или подобный RAII надо реализовать с помощью каких-нибудь возможностей unsafe?