7

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?

Ayman Madkour
  • 325
  • 1
  • 7
  • While `Cursor` is alive, you want nobody else to modify your `HashMap`? Is that the purpose of all that? – hellow Nov 23 '18 at 07:12
  • @hellow correct. – Ayman Madkour Nov 23 '18 at 07:22
  • [This](https://github.com/Kimundi/owning-ref-rs) could help? – vikram2784 Nov 23 '18 at 07:46
  • 1
    Could you put your code into the [playground](https://play.rust-lang.org/) so that we can more easily play around with it? – Sebastian Redl Nov 23 '18 at 07:55
  • Btw. it is very unidiotmatic to use `return` as a last statement. Instead just remove `return` and the `;` , e.g. `fn foo() -> u32 { 3 }` – hellow Nov 23 '18 at 07:57
  • @SebastianRedl Here you go: https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=5e24db3ac05cef2402007243215429b3 – Ayman Madkour Nov 23 '18 at 08:24
  • @hellow Even some of the core developers prefer to always use `return` for consistency, so I wouldn't call this "unidiomatic"; it's just a question of personal style. – Sven Marnach Nov 23 '18 at 09:18
  • @SvenMarnach https://stackoverflow.com/questions/27961879/why-is-using-return-as-the-last-statement-in-a-function-considered-bad-style https://doc.rust-lang.org/book/first-edition/functions.html#early-returns: *"Using a `return` as the last line of a function works, but is considered poor style:"* – hellow Nov 23 '18 at 09:32
  • Is it necessary for you have the iterator inside the `Cursor` ? If not, [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=0acecf4ddf8dc91640c6e858fe2ad74d) is another way (may not be ideal) – vikram2784 Nov 23 '18 at 09:48
  • @AymanMadkour You are essentially trying to create a self-referential struct, which is not easily possible in Rust (see [this question](https://stackoverflow.com/q/32300132/279627) for more information). – Sven Marnach Nov 23 '18 at 10:14
  • 1
    @hellow I know. There's also [this question](https://stackoverflow.com/q/27961879/279627) and a Clippy lint. I personally don't use the `return` in the last statement, but don't feel strongly about what other people should do. – Sven Marnach Nov 23 '18 at 10:19
  • 1
    Possible duplicate of [Why can't I store a value and a reference to that value in the same struct?](https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct) – Peter Hall Nov 23 '18 at 14:21

1 Answers1

6

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

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841