Hey all, I'm currently learning about CRDTs and so, I tried to implement the most basic CRDT, the increment counter. While running the tests I noticed that the merge method sometimes fails. I started by running using rustc 1.58.1 but updating to rustc 1.61.0 yields the same results.
Here's the code:
use std::{collections::HashMap, hash::Hash, ops::Deref};
pub struct IncrementCounter<Id>
where
Id: Eq + Hash + Default + Deref,
{
id: Id,
history: HashMap<Id, u32>,
}
impl<Id> IncrementCounter<Id>
where
Id: Eq + Hash + Default + Deref,
{
pub fn init<const N_REPLICAS: usize>(id: Id, ids: [Id; N_REPLICAS]) -> Self {
let mut history = HashMap::with_capacity(N_REPLICAS);
for id in ids {
history.insert(id, 0);
}
Self { id, history }
}
pub fn increment(&mut self) {
let n = self.history.get_mut(&self.id).unwrap();
*n += 1;
}
pub fn merge(&mut self, history: &HashMap<Id, u32>) {
self.history
.values_mut()
.zip(history.values())
.for_each(|(l, r)| {
*l = u32::max(*l, *r);
});
}
}
#[cfg(test)]
mod test {
use crate::IncrementCounter;
use std::collections::HashMap;
#[test]
fn test_merge() {
let mut counter_n = IncrementCounter::init::<2>("n", ["n", "m"]);
let mut counter_m = IncrementCounter::init::<2>("m", ["n", "m"]);
// Increment N
for _ in 1..=10 {
counter_n.increment();
}
// Increment M
for _ in 1..=5 {
counter_m.increment()
}
// Merge M & N
counter_n.merge(&counter_m.history);
counter_m.merge(&counter_n.history);
// Test
let mut expected = HashMap::with_capacity(2);
expected.insert("n", 10);
expected.insert("m", 5);
assert_eq!(counter_n.history, expected);
assert_eq!(counter_m.history, expected);
}
}