Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Map

Map<K, V> is a replicated, unordered, eventually consistent key-value map. Internally it is backed by an im::HashMap with a deterministic hasher.

Construction

use mosaik::collections::{Map, StoreId, SyncConfig};

// Writer — can read and write
let map = Map::<String, u64>::writer(&network, StoreId::from("balances"));

// Writer with custom sync config
let map = Map::<String, u64>::writer_with_config(&network, store_id, config);

// Reader — read-only, deprioritized for leadership
let map = Map::<String, u64>::reader(&network, store_id);

// Reader with custom sync config
let map = Map::<String, u64>::reader_with_config(&network, store_id, config);

// Aliases: new() == writer(), new_with_config() == writer_with_config()
let map = Map::<String, u64>::new(&network, store_id);

Read operations

Available on both writers and readers. All reads operate on the local committed state — they are non-blocking and never touch the network.

MethodTimeDescription
len() -> usizeO(1)Number of entries
is_empty() -> boolO(1)Whether the map is empty
contains_key(&K) -> boolO(log n)Test if a key exists
get(&K) -> Option<V>O(log n)Get a clone of the value for a key
iter() -> impl Iterator<Item = (K, V)>O(1)*Iterate over all key-value pairs
keys() -> impl Iterator<Item = K>O(1)*Iterate over all keys
values() -> impl Iterator<Item = V>O(1)*Iterate over all values
version() -> VersionO(1)Current committed state version
when() -> &WhenO(1)Access the state observer

* Iterator creation is O(1) due to im::HashMap’s structural sharing; full iteration is O(n).

// Read the current state
if let Some(balance) = map.get(&"alice".into()) {
    println!("Alice's balance: {balance}");
}

// Snapshot iteration — takes a structural clone, then iterates
for (key, value) in map.iter() {
    println!("{key}: {value}");
}

Write operations

Only available on MapWriter<K, V>. All writes go through Raft consensus and return the Version at which the mutation will be committed.

MethodDescription
insert(K, V) -> Result<Version, Error<(K, V)>>Insert or update a key-value pair
compare_exchange(K, Option<V>, Option<V>) -> Result<Version, Error<(K, Option<V>, Option<V>)>>Atomic compare-and-swap for a key
remove(&K) -> Result<Version, Error<K>>Remove a key
extend(impl IntoIterator<Item = (K, V)>) -> Result<Version, Error<Vec<(K, V)>>>Batch insert
clear() -> Result<Version, Error<()>>Remove all entries
// Insert a single entry
let v = map.insert("ETH".into(), 3812).await?;

// Batch insert
let v = map.extend([
    ("BTC".into(), 105_000),
    ("SOL".into(), 178),
]).await?;

// Wait for the batch to commit
map.when().reaches(v).await;

// Remove
map.remove(&"SOL".into()).await?;

// Atomic compare-and-swap: update only if current value matches
let v = map.compare_exchange(
    "ETH".into(),
    Some(3812),   // expected current value
    Some(3900),   // new value
).await?;
map.when().reaches(v).await;

// Compare-and-swap to insert a new key (expected = None)
let v = map.compare_exchange(
    "DOGE".into(),
    None,          // key must not exist
    Some(42),      // value to insert
).await?;

// Compare-and-swap to delete (new = None)
let v = map.compare_exchange(
    "DOGE".into(),
    Some(42),      // expected current value
    None,          // remove the key
).await?;

// Clear everything
map.clear().await?;

Compare-and-swap semantics

compare_exchange atomically checks the value associated with a key and only applies the mutation if it matches the expected parameter:

  • key: The key to operate on.
  • expected: The expected current value (None means the key must not exist).
  • new: The value to write if the expectation holds (None removes the key).
expectednewEffect when matched
NoneSome(v)Insert a new key-value pair
Some(old)Some(new)Update an existing value
Some(old)NoneRemove the key
NoneNoneNo-op (key must not exist)

If the current value does not match expected, the operation is a no-op — it still commits to the Raft log (incrementing the version) but does not mutate the map.

Error handling

On failure, the values you attempted to write are returned inside the error so you can retry without re-creating them:

match map.insert("key".into(), expensive_value).await {
    Ok(version) => println!("committed at {version}"),
    Err(Error::Offline((key, value))) => {
        // Node is temporarily offline — retry later
        println!("offline, got {key} and {value} back");
    }
    Err(Error::NetworkDown) => {
        // Permanent failure
    }
}

Status & observation

// Wait until the map is online
map.when().online().await;

// Wait for a specific version
let v = map.insert("x".into(), 1).await?;
map.when().reaches(v).await;

// Wait for any new commit
map.when().updated().await;

// Detect going offline
map.when().offline().await;

Group identity

The group key for a Map<K, V> is derived from:

UniqueId::from("mosaik_collections_map")
    .derive(store_id)
    .derive(type_name::<K>())
    .derive(type_name::<V>())

Two maps with the same StoreId but different key or value types will be in completely separate consensus groups.