forma/composition/
interner.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::hash::Hash;
6use std::ops::Deref;
7use std::rc::Rc;
8
9use rustc_hash::FxHashSet;
10
11#[derive(Debug)]
12pub struct Interned<T: Eq + Hash> {
13    data: Rc<T>,
14}
15
16impl<T: Eq + Hash> Deref for Interned<T> {
17    type Target = T;
18
19    fn deref(&self) -> &Self::Target {
20        &self.data
21    }
22}
23
24// Safe as long as `Interned` cannot clone the `Rc`.
25unsafe impl<T: Eq + Hash + Sync> Sync for Interned<T> {}
26
27#[derive(Debug, Default)]
28pub struct Interner<T> {
29    // We need to use `Rc` instead of `Weak` since `Weak` can have it's hash
30    // value changed on the fly which is unsupported by `HashSet`.
31    pointers: FxHashSet<Rc<T>>,
32}
33
34impl<T: Eq + Hash> Interner<T> {
35    pub fn get(&mut self, val: T) -> Interned<T> {
36        if let Some(rc) = self.pointers.get(&val) {
37            return Interned { data: Rc::clone(rc) };
38        }
39
40        let data = Rc::new(val);
41
42        self.pointers.insert(Rc::clone(&data));
43
44        Interned { data }
45    }
46
47    pub fn compact(&mut self) {
48        self.pointers.retain(|rc| Rc::strong_count(rc) > 1);
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn interned_same_ptr() {
58        let mut interner = Interner::default();
59
60        let foo0 = interner.get(String::from("foo"));
61        let foo1 = interner.get(String::from("foo"));
62        let bar = interner.get(String::from("bar"));
63
64        assert!(Rc::ptr_eq(&foo0.data, &foo1.data));
65        assert!(!Rc::ptr_eq(&foo0.data, &bar.data));
66    }
67
68    #[test]
69    fn interned_drop_data() {
70        let mut interner = Interner::default();
71
72        let foo0 = interner.get(String::from("foo"));
73
74        {
75            let _foo1 = interner.get(String::from("foo"));
76            let _bar = interner.get(String::from("bar"));
77
78            assert_eq!(interner.pointers.len(), 2);
79        }
80
81        assert_eq!(interner.pointers.len(), 2);
82
83        interner.compact();
84
85        assert_eq!(interner.pointers.len(), 1);
86
87        let foo1 = interner.get(String::from("foo"));
88        assert!(Rc::ptr_eq(&foo0.data, &foo1.data));
89    }
90}