Skip to main content

crdt_kit/
lww_register.rs

1use std::time::{SystemTime, UNIX_EPOCH};
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)]
23pub struct LWWRegister<T: Clone> {
24    actor: String,
25    value: T,
26    timestamp: u64,
27}
28
29impl<T: Clone + PartialEq> PartialEq for LWWRegister<T> {
30    fn eq(&self, other: &Self) -> bool {
31        self.value == other.value && self.timestamp == other.timestamp
32    }
33}
34
35impl<T: Clone + Eq> Eq for LWWRegister<T> {}
36
37impl<T: Clone> LWWRegister<T> {
38    /// Create a new LWW-Register with an initial value.
39    ///
40    /// The timestamp is automatically set to the current system time.
41    pub fn new(actor: impl Into<String>, value: T) -> Self {
42        Self {
43            actor: actor.into(),
44            value,
45            timestamp: now(),
46        }
47    }
48
49    /// Create a new LWW-Register with an explicit timestamp.
50    ///
51    /// Useful for testing or when you need deterministic behavior.
52    pub fn with_timestamp(actor: impl Into<String>, value: T, timestamp: u64) -> Self {
53        Self {
54            actor: actor.into(),
55            value,
56            timestamp,
57        }
58    }
59
60    /// Update the register's value.
61    ///
62    /// The timestamp is automatically set to the current system time.
63    pub fn set(&mut self, value: T) {
64        self.value = value;
65        self.timestamp = now();
66    }
67
68    /// Update the register's value with an explicit timestamp.
69    pub fn set_with_timestamp(&mut self, value: T, timestamp: u64) {
70        if timestamp >= self.timestamp {
71            self.value = value;
72            self.timestamp = timestamp;
73        }
74    }
75
76    /// Get the current value.
77    #[must_use]
78    pub fn value(&self) -> &T {
79        &self.value
80    }
81
82    /// Get the current timestamp.
83    #[must_use]
84    pub fn timestamp(&self) -> u64 {
85        self.timestamp
86    }
87
88    /// Get this replica's actor ID.
89    #[must_use]
90    pub fn actor(&self) -> &str {
91        &self.actor
92    }
93}
94
95impl<T: Clone> Crdt for LWWRegister<T> {
96    fn merge(&mut self, other: &Self) {
97        if other.timestamp > self.timestamp
98            || (other.timestamp == self.timestamp && other.actor > self.actor)
99        {
100            self.value = other.value.clone();
101            self.timestamp = other.timestamp;
102        }
103    }
104}
105
106fn now() -> u64 {
107    SystemTime::now()
108        .duration_since(UNIX_EPOCH)
109        .unwrap_or_default()
110        .as_micros() as u64
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn new_register_holds_value() {
119        let r = LWWRegister::with_timestamp("a", 42, 1);
120        assert_eq!(*r.value(), 42);
121    }
122
123    #[test]
124    fn set_updates_value() {
125        let mut r = LWWRegister::with_timestamp("a", 1, 1);
126        r.set_with_timestamp(2, 2);
127        assert_eq!(*r.value(), 2);
128    }
129
130    #[test]
131    fn merge_keeps_later_timestamp() {
132        let mut r1 = LWWRegister::with_timestamp("a", "old", 1);
133        let r2 = LWWRegister::with_timestamp("b", "new", 2);
134
135        r1.merge(&r2);
136        assert_eq!(*r1.value(), "new");
137    }
138
139    #[test]
140    fn merge_keeps_self_if_later() {
141        let mut r1 = LWWRegister::with_timestamp("a", "new", 2);
142        let r2 = LWWRegister::with_timestamp("b", "old", 1);
143
144        r1.merge(&r2);
145        assert_eq!(*r1.value(), "new");
146    }
147
148    #[test]
149    fn merge_breaks_tie_by_actor() {
150        let mut r1 = LWWRegister::with_timestamp("a", "first", 1);
151        let r2 = LWWRegister::with_timestamp("b", "second", 1);
152
153        r1.merge(&r2);
154        // "b" > "a", so r2 wins the tie
155        assert_eq!(*r1.value(), "second");
156    }
157
158    #[test]
159    fn merge_is_idempotent() {
160        let mut r1 = LWWRegister::with_timestamp("a", "x", 1);
161        let r2 = LWWRegister::with_timestamp("b", "y", 2);
162
163        r1.merge(&r2);
164        let after_first = r1.clone();
165        r1.merge(&r2);
166
167        assert_eq!(r1, after_first);
168    }
169}