Architecture

To share the database state across async tasks, I wrapped the HashMap in an Arc (Atomic Reference Count) and a Mutex.

// src/db.rs
#[derive(Clone)]
pub struct Db {
    // Mutex ensures only one thread writes at a time
    state: Arc<Mutex<DbState>>,
}

struct DbState {
    // Key-Value store. 
    kv: HashMap<String, (DataType, Option<Instant>)>,
}

Lazy Expiration

Instead of a background thread to continously scan and check our values for expired time. I decided to just go with lazy checking. Every time a key is accessed via get(), we check if it has expired before returning it.

// src/db.rs
pub fn get(&self, key: &str) -> Option<DataType> {
    let mut lock = self.state.lock().unwrap();

    if let Some((_val, Some(expiry))) = lock.kv.get(key) {
        // If the current time is past the expiry, delete it immediately.
        if Instant::now() > *expiry {
            lock.kv.remove(key);
            return None;
        }
    }

    // Return the value if valid
    lock.kv.get(key).map(|(val, _)| val.clone())
}