1use std::collections::HashSet;
2use std::sync::RwLock;
34/// 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>>>);
89impl<T> OnceSet<T>
10where
11T: std::cmp::Eq + std::hash::Hash,
12{
13pub const fn new() -> Self {
14Self(RwLock::new(None))
15 }
1617/// Insert `val` into the set. Returns `false` if `val` had previously been added to the set;
18 /// otherwise returns `true`.
19pub fn insert(&self, val: T) -> bool {
20// first check with a (cheap) read-lock
21if self
22.0
23.read()
24 .unwrap()
25 .as_ref()
26 .map(|x| x.contains(&val))
27 .unwrap_or(false)
28 {
29// already added
30return false;
31 }
3233// 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.
36self.0
37.write()
38 .unwrap()
39 .get_or_insert_with(HashSet::new)
40 .insert(val)
41 }
42}
4344#[cfg(test)]
45mod tests {
46use super::*;
4748#[test]
49fn test_once_set() {
50let set = OnceSet::new();
5152assert!(set.insert("FOO".to_string()));
53assert!(set.insert("BAR".to_string()));
54assert!(!set.insert("FOO".to_string()));
55assert!(!set.insert("BAR".to_string()));
56assert!(!set.insert("BAR".to_string()));
57assert!(set.insert("XYZ".to_string()));
58assert!(!set.insert("XYZ".to_string()));
59 }
60}