Курсор записей HashMap с RwLockGuard в Rust

Я новичок в Rust, и я пытаюсь реализовать простое, потокобезопасное хранилище ключей и значений в памяти, используя HashMap, защищенный в RwLock. Мой код выглядит так:

use std::sync::{ Arc, RwLock, RwLockReadGuard };
use std::collections::HashMap;
use std::collections::hash_map::Iter;

type SimpleCollection = HashMap<String, String>;

struct Store(Arc<RwLock<SimpleCollection>>);

impl Store {
    fn new() -> Store { return Store(Arc::new(RwLock::new(SimpleCollection::new()))) }

    fn get(&self, key: &str) -> Option<String> {
        let map = self.0.read().unwrap();
        return map.get(&key.to_string()).map(|s| s.clone());
    }

    fn set(&self, key: &str, value: &str) {
        let mut map = self.0.write().unwrap();
        map.insert(key.to_string(), value.to_string());
    }
}

Пока этот код работает нормально. Проблема в том, что я пытаюсь реализовать функцию scan(), которая возвращает объект Cursor, который можно использовать для перебора всех записей. Я хочу, чтобы объект Cursor содержал RwLockGuard, который не освобождается до тех пор, пока сам курсор не будет освобожден (в основном я не хочу разрешать модификации, пока курсор жив).

Я пробовал это:

use ...

type SimpleCollection = HashMap<String, String>;

struct Store(Arc<RwLock<SimpleCollection>>);

impl Store {
    ...

    fn scan(&self) -> Cursor {
        let guard = self.0.read().unwrap();
        let iter = guard.iter();
        return Cursor { guard, iter };
    }
}

struct Cursor<'l> {
    guard: RwLockReadGuard<'l, SimpleCollection>,
    iter: Iter<'l, String, String>
}

impl<'l> Cursor<'l> {
    fn next(&mut self) -> Option<(String, String)> {
        return self.iter.next().map(|r| (r.0.clone(), r.1.clone()));
    }
}

Но это не сработало, так как я получил эту ошибку компиляции:

error[E0597]: `guard` does not live long enough
  --> src/main.rs:24:20
   |
24 |         let iter = guard.iter();
   |                    ^^^^^ borrowed value does not live long enough
25 |         return Cursor { guard, iter };
26 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 22:5...
  --> src/main.rs:22:5
   |
22 | /     fn scan(&self) -> Cursor {
23 | |         let guard = self.0.read().unwrap();
24 | |         let iter = guard.iter();
25 | |         return Cursor { guard, iter };
26 | |     }
   | |_____^

Любые идеи?


person Ayman Madkour    schedule 23.11.2018    source источник
comment
Пока Cursor жив, вы хотите, чтобы никто другой не модифицировал ваш HashMap? Это цель всего этого?   -  person hellow    schedule 23.11.2018
comment
@привет, правильно.   -  person Ayman Madkour    schedule 23.11.2018
comment
Это может помочь?   -  person vikram2784    schedule 23.11.2018
comment
Не могли бы вы поместить свой код на игровую площадку, чтобы нам было проще с ним поиграться?   -  person Sebastian Redl    schedule 23.11.2018
comment
Кстати. очень однообразно использовать return в качестве последнего оператора. Вместо этого просто удалите return и ; , например. fn foo() -> u32 { 3 }   -  person hellow    schedule 23.11.2018
comment
@hellow Даже некоторые основные разработчики предпочитают всегда использовать return для согласованности, поэтому я бы не назвал это однообразным; это просто вопрос личного стиля.   -  person Sven Marnach    schedule 23.11.2018
comment
@SvenMarnach stackoverflow.com/questions/27961879/ doc.rust-lang.org/book/first-edition/: Использование return в качестве последней строки функции работает, но считается плохим стилем:   -  person hellow    schedule 23.11.2018
comment
Вам необходимо иметь итератор внутри Cursor ? Если нет, это другой способ (возможно, не идеально)   -  person vikram2784    schedule 23.11.2018
comment
@AymanMadkour По сути, вы пытаетесь создать самореферентную структуру, что нелегко сделать в Rust (см. этот вопрос для Дополнительная информация).   -  person Sven Marnach    schedule 23.11.2018
comment
@привет, я знаю. Также есть этот вопрос и линт Clippy. Я лично не использую return в последнем утверждении, но не очень понимаю, что должны делать другие люди.   -  person Sven Marnach    schedule 23.11.2018
comment


Ответы (1)


Как упоминалось в комментариях, проблема в том, что структуры обычно не могут ссылаться на себя в Rust. Структура Cursor, которую вы пытаетесь создать, содержит как MutexGuard, так и итератор, заимствующий MutexGuard, что невозможно (по уважительным причинам — см. связанный вопрос).

Самое простое решение в этом случае - ввести отдельную структуру, хранящую MutexGuard, например.

struct StoreLock<'a> {
    guard: RwLockReadGuard<'a, SimpleCollection>,
}

Затем на Store мы можем ввести метод, возвращающий StoreLock

fn lock(&self) -> StoreLock {
    StoreLock { guard: self.0.read().unwrap() }
}

и StoreLock может выставить фактический метод scan() (и, возможно, другие, требующие постоянной блокировки):

impl<'a> StoreLock<'a> {
    fn scan(&self) -> Cursor {
        Cursor { iter: self.guard.iter() }
    }
}

Сама структура Cursor содержит только итератор:

struct Cursor<'a> {
    iter: Iter<'a, String, String>,
}

Код клиента сначала должен получить блокировку, а затем получить курсор:

let lock = s.lock();
let cursor = lock.scan();

Это гарантирует, что блокировка проживет достаточно долго, чтобы завершить сканирование.

Полный код на игровой площадке

person Sven Marnach    schedule 23.11.2018
comment
Большое спасибо, @SvenMarnach. Думаю, я все еще не привык к мышлению Rust. - person Ayman Madkour; 23.11.2018