Skip to main content

crdt_kit/
lww_register.rs

1use alloc::string::String;
2
3use crate::Crdt;
4
5/// A last-writer-wins register (LWW-Register).
6///
7/// Resolves concurrent writes by keeping the value with the highest timestamp.
8/// Ties are broken by comparing actor IDs lexicographically.
9///
10/// # Example
11///
12/// ```
13/// use crdt_kit::prelude::*;
14///
15/// let mut r1 = LWWRegister::new("node-1", "hello");
16/// let mut r2 = LWWRegister::new("node-2", "world");
17///
18/// // The register with the later timestamp wins
19/// r1.merge(&r2);
20/// // Value is either "hello" or "world" depending on timestamps
21/// ```
22#[derive(Debug, Clone)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24pub struct LWWRegister<T: Clone> {
25    actor: String,
26    value: T,
27    timestamp: u64,
28}
29
30impl<T: Clone + PartialEq> PartialEq for LWWRegister<T> {
31    fn eq(&self, other: &Self) -> bool {
32        self.value == other.value && self.timestamp == other.timestamp
33    }
34}
35
36impl<T: Clone + Eq> Eq for LWWRegister<T> {}
37
38impl<T: Clone> LWWRegister<T> {
39    /// Create a new LWW-Register with an initial value.
40    ///
41    /// The timestamp is automatically set to the current system time.
42    ///
43    /// This method requires the `std` feature. In `no_std` environments, use
44    /// [`LWWRegister::with_timestamp`] instead.
45    #[cfg(feature = "std")]
46    pub fn new(actor: impl Into<String>, value: T) -> Self {
47        Self {
48            actor: actor.into(),
49            value,
50            timestamp: now(),
51        }
52    }
53
54    /// Create a new LWW-Register with an explicit timestamp.
55    ///
56    /// Useful for testing or when you need deterministic behavior.
57    /// This is the only constructor available in `no_std` environments.
58    pub fn with_timestamp(actor: impl Into<String>, value: T, timestamp: u64) -> Self {
59        Self {
60            actor: actor.into(),
61            value,
62            timestamp,
63        }
64    }
65
66    /// Update the register's value.
67    ///
68    /// The timestamp is automatically set to the current system time.
69    ///
70    /// This method requires the `std` feature. In `no_std` environments, use
71    /// [`LWWRegister::set_with_timestamp`] instead.
72    #[cfg(feature = "std")]
73    pub fn set(&mut self, value: T) {
74        self.value = value;
75        self.timestamp = now();
76    }
77
78    /// Update the register's value with an explicit timestamp.
79    pub fn set_with_timestamp(&mut self, value: T, timestamp: u64) {
80        if timestamp >= self.timestamp {
81            self.value = value;
82            self.timestamp = timestamp;
83        }
84    }
85
86    /// Get the current value.
87    #[must_use]
88    pub fn value(&self) -> &T {
89        &self.value
90    }
91
92    /// Get the current timestamp.
93    #[must_use]
94    pub fn timestamp(&self) -> u64 {
95        self.timestamp
96    }
97
98    /// Get this replica's actor ID.
99    #[must_use]
100    pub fn actor(&self) -> &str {
101        &self.actor
102    }
103}
104
105impl<T: Clone> Crdt for LWWRegister<T> {
106    fn merge(&mut self, other: &Self) {
107        if other.timestamp > self.timestamp
108            || (other.timestamp == self.timestamp && other.actor > self.actor)
109        {
110            self.value = other.value.clone();
111            self.timestamp = other.timestamp;
112        }
113    }
114}
115
116#[cfg(feature = "std")]
117fn now() -> u64 {
118    std::time::SystemTime::now()
119        .duration_since(std::time::UNIX_EPOCH)
120        .unwrap_or_default()
121        .as_micros() as u64
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn new_register_holds_value() {
130        let r = LWWRegister::with_timestamp("a", 42, 1);
131        assert_eq!(*r.value(), 42);
132    }
133
134    #[test]
135    fn set_updates_value() {
136        let mut r = LWWRegister::with_timestamp("a", 1, 1);
137        r.set_with_timestamp(2, 2);
138        assert_eq!(*r.value(), 2);
139    }
140
141    #[test]
142    fn merge_keeps_later_timestamp() {
143        let mut r1 = LWWRegister::with_timestamp("a", "old", 1);
144        let r2 = LWWRegister::with_timestamp("b", "new", 2);
145
146        r1.merge(&r2);
147        assert_eq!(*r1.value(), "new");
148    }
149
150    #[test]
151    fn merge_keeps_self_if_later() {
152        let mut r1 = LWWRegister::with_timestamp("a", "new", 2);
153        let r2 = LWWRegister::with_timestamp("b", "old", 1);
154
155        r1.merge(&r2);
156        assert_eq!(*r1.value(), "new");
157    }
158
159    #[test]
160    fn merge_breaks_tie_by_actor() {
161        let mut r1 = LWWRegister::with_timestamp("a", "first", 1);
162        let r2 = LWWRegister::with_timestamp("b", "second", 1);
163
164        r1.merge(&r2);
165        // "b" > "a", so r2 wins the tie
166        assert_eq!(*r1.value(), "second");
167    }
168
169    #[test]
170    fn merge_is_idempotent() {
171        let mut r1 = LWWRegister::with_timestamp("a", "x", 1);
172        let r2 = LWWRegister::with_timestamp("b", "y", 2);
173
174        r1.merge(&r2);
175        let after_first = r1.clone();
176        r1.merge(&r2);
177
178        assert_eq!(r1, after_first);
179    }
180}