1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
use std::collections::HashSet;
use std::sync::RwLock;

/// A [`HashSet`] that only allows insertions and uses interior mutablity. This allows it to be used
/// in a global static.
#[derive(Debug, Default)]
pub struct OnceSet<T>(RwLock<Option<HashSet<T>>>);

impl<T> OnceSet<T>
where
    T: std::cmp::Eq + std::hash::Hash,
{
    pub const fn new() -> Self {
        Self(RwLock::new(None))
    }

    /// Insert `val` into the set. Returns `false` if `val` had previously been added to the set;
    /// otherwise returns `true`.
    pub fn insert(&self, val: T) -> bool {
        // first check with a (cheap) read-lock
        if self
            .0
            .read()
            .unwrap()
            .as_ref()
            .map(|x| x.contains(&val))
            .unwrap_or(false)
        {
            // already added
            return false;
        }

        // If it looks like we haven't already added the value, add it to the set. Also detect the
        // (rare) case that another thread already added the value after we released the read-lock
        // above.
        self.0
            .write()
            .unwrap()
            .get_or_insert_with(HashSet::new)
            .insert(val)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_once_set() {
        let set = OnceSet::new();

        assert!(set.insert("FOO".to_string()));
        assert!(set.insert("BAR".to_string()));
        assert!(!set.insert("FOO".to_string()));
        assert!(!set.insert("BAR".to_string()));
        assert!(!set.insert("BAR".to_string()));
        assert!(set.insert("XYZ".to_string()));
        assert!(!set.insert("XYZ".to_string()));
    }
}