shadow_rs/utility/
once_set.rs

1use std::collections::HashSet;
2use std::sync::RwLock;
3
4/// A [`HashSet`] that only allows insertions and uses interior mutablity. This allows it to be used
5/// in a global static.
6#[derive(Debug, Default)]
7pub struct OnceSet<T>(RwLock<Option<HashSet<T>>>);
8
9impl<T> OnceSet<T>
10where
11    T: std::cmp::Eq + std::hash::Hash,
12{
13    pub const fn new() -> Self {
14        Self(RwLock::new(None))
15    }
16
17    /// Insert `val` into the set. Returns `false` if `val` had previously been added to the set;
18    /// otherwise returns `true`.
19    pub fn insert(&self, val: T) -> bool {
20        // first check with a (cheap) read-lock
21        if self
22            .0
23            .read()
24            .unwrap()
25            .as_ref()
26            .map(|x| x.contains(&val))
27            .unwrap_or(false)
28        {
29            // already added
30            return false;
31        }
32
33        // If it looks like we haven't already added the value, add it to the set. Also detect the
34        // (rare) case that another thread already added the value after we released the read-lock
35        // above.
36        self.0
37            .write()
38            .unwrap()
39            .get_or_insert_with(HashSet::new)
40            .insert(val)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn test_once_set() {
50        let set = OnceSet::new();
51
52        assert!(set.insert("FOO".to_string()));
53        assert!(set.insert("BAR".to_string()));
54        assert!(!set.insert("FOO".to_string()));
55        assert!(!set.insert("BAR".to_string()));
56        assert!(!set.insert("BAR".to_string()));
57        assert!(set.insert("XYZ".to_string()));
58        assert!(!set.insert("XYZ".to_string()));
59    }
60}