Skip to main content

uni_crdt/
gset.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2026 Dragonscale Team
3
4use crate::CrdtMerge;
5use fxhash::FxHashSet;
6use serde::{Deserialize, Serialize};
7use std::hash::Hash;
8
9/// A Grow-only Set (GSet).
10///
11/// Elements can only be added, never removed.
12/// Merging takes the union of two sets.
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct GSet<T: Hash + Eq + Clone> {
15    elements: FxHashSet<T>,
16}
17
18impl<T: Hash + Eq + Clone> Default for GSet<T> {
19    fn default() -> Self {
20        Self {
21            elements: FxHashSet::default(),
22        }
23    }
24}
25
26impl<T: Hash + Eq + Clone> GSet<T> {
27    /// Create a new, empty GSet.
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    /// Add an element to the set.
33    pub fn add(&mut self, element: T) {
34        self.elements.insert(element);
35    }
36
37    /// Check if an element is in the set.
38    pub fn contains(&self, element: &T) -> bool {
39        self.elements.contains(element)
40    }
41
42    /// Returns an iterator over all elements in the set.
43    pub fn elements(&self) -> impl Iterator<Item = &T> {
44        self.elements.iter()
45    }
46
47    /// Returns the number of elements in the set.
48    pub fn len(&self) -> usize {
49        self.elements.len()
50    }
51
52    /// Returns true if the set is empty.
53    pub fn is_empty(&self) -> bool {
54        self.elements.is_empty()
55    }
56}
57
58impl<T: Hash + Eq + Clone> CrdtMerge for GSet<T> {
59    fn merge(&mut self, other: &Self) {
60        for element in &other.elements {
61            self.elements.insert(element.clone());
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_add() {
72        let mut gs = GSet::new();
73        gs.add("apple".to_string());
74        gs.add("banana".to_string());
75        assert!(gs.contains(&"apple".to_string()));
76        assert!(gs.contains(&"banana".to_string()));
77        assert!(!gs.contains(&"cherry".to_string()));
78        assert_eq!(gs.len(), 2);
79    }
80
81    #[test]
82    fn test_merge() {
83        let mut a = GSet::new();
84        a.add(1);
85        a.add(2);
86
87        let mut b = GSet::new();
88        b.add(2);
89        b.add(3);
90
91        a.merge(&b);
92
93        assert!(a.contains(&1));
94        assert!(a.contains(&2));
95        assert!(a.contains(&3));
96        assert_eq!(a.len(), 3);
97    }
98}