The In-Memory Version

Define the API against a HashMap first, so the interface is settled.

Part 1 of 3Updated

Get the interface right before you touch the disk.

pub trait KvStore {
    fn set(&mut self, key: String, value: String) -> Result<()>;
    fn get(&self, key: &str) -> Result<Option<String>>;
    fn remove(&mut self, key: &str) -> Result<()>;
}

Every method returns Result even though a HashMap can’t fail. That’s deliberate: the disk-backed version will fail, and changing the signature later means changing every call site. Design the trait for the implementation you’re heading toward.

get returns Result<Option<String>> — the two failure modes are genuinely different. “The key isn’t there” is Ok(None); “the disk is on fire” is Err.