Cursor of HashMap records with RwLockGuard in Rust

1.2k views Asked by At

I am new to Rust, and I am trying to implement a simple, thread-safe memory key-value store, using a HashMap protected within a RwLock. My code looks like this:

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());
    }
}

So far, this code works OK. The problem is that I am trying to implement a scan() function, which returns a Cursor object that can be used to iterate over all the records. I want the Cursor object to hold a RwLockGuard, which is not released until the cursor itself is released (basically I don't want to allow modifications while a Cursor is alive).

I tried this:

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()));
    }
}

But that did not work, as I got this compilation error:

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 | |     }
   | |_____^

Any ideas?

1

There are 1 answers

1
Sven Marnach On BEST ANSWER

As mentioned in the comments, the problem is that structs generally can't be self-referential in Rust. The Cursor struct you are trying to construct contains both the MutexGuard and the iterator borrowing the MutexGuard, which is not possible (for good reasons – see the linked question).

The easiest fix in this case is to introduce a separate struct storing the MutexGuard, e.g.

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

On the Store, we can then introduce a method returning a StoreLock

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

and the StoreLock can expose the actual scan() method (and possibly others requiring a persistent lock):

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

The Cursor struct itself only contains the iterator:

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

Client code first needs to obtain the lock, then get the cursor:

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

This ensures that the lock lives long enough to finish scanning.

Full code on the playground