Skip to main content

flag_rs/
string_pool.rs

1//! String interning pool for reducing memory usage
2//!
3//! This module provides a string interning mechanism to avoid storing
4//! duplicate strings across the application, particularly useful for
5//! flag names and command names in large CLIs.
6
7use std::collections::HashSet;
8use std::sync::{Arc, Mutex};
9
10/// A thread-safe string interning pool
11///
12/// This structure ensures that identical strings are stored only once in memory,
13/// returning shared references (`Arc<str>`) to the same underlying data.
14#[derive(Clone)]
15pub struct StringPool {
16    pool: Arc<Mutex<HashSet<Arc<str>>>>,
17}
18
19impl StringPool {
20    /// Creates a new empty string pool
21    #[must_use]
22    pub fn new() -> Self {
23        Self {
24            pool: Arc::new(Mutex::new(HashSet::new())),
25        }
26    }
27
28    /// Interns a string, returning a shared reference
29    ///
30    /// If the string already exists in the pool, returns the existing reference.
31    /// Otherwise, adds it to the pool and returns a new reference.
32    ///
33    /// # Arguments
34    ///
35    /// * `s` - The string to intern
36    ///
37    /// # Returns
38    ///
39    /// An `Arc<str>` pointing to the interned string
40    #[must_use]
41    pub fn intern(&self, s: &str) -> Arc<str> {
42        if let Ok(mut pool) = self.pool.lock() {
43            // Check if string already exists
44            if let Some(existing) = pool.get(s) {
45                return Arc::clone(existing);
46            }
47
48            // Add new string to pool
49            let arc_str: Arc<str> = Arc::from(s);
50            pool.insert(Arc::clone(&arc_str));
51            arc_str
52        } else {
53            // Fallback if lock fails
54            Arc::from(s)
55        }
56    }
57
58    /// Returns the number of unique strings in the pool
59    #[must_use]
60    pub fn size(&self) -> usize {
61        self.pool.lock().map_or(0, |p| p.len())
62    }
63
64    /// Clears all strings from the pool
65    pub fn clear(&self) {
66        if let Ok(mut pool) = self.pool.lock() {
67            pool.clear();
68        }
69    }
70}
71
72impl Default for StringPool {
73    fn default() -> Self {
74        Self::new()
75    }
76}
77
78/// Global string pool instance
79///
80/// This is created lazily on first use.
81fn global_pool() -> &'static StringPool {
82    static POOL: std::sync::OnceLock<StringPool> = std::sync::OnceLock::new();
83    POOL.get_or_init(StringPool::new)
84}
85
86/// Interns a string in the global pool
87#[must_use]
88pub fn intern(s: &str) -> Arc<str> {
89    global_pool().intern(s)
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_string_interning() {
98        let pool = StringPool::new();
99
100        let s1 = pool.intern("hello");
101        let s2 = pool.intern("hello");
102        let s3 = pool.intern("world");
103
104        // s1 and s2 should point to the same memory
105        assert!(Arc::ptr_eq(&s1, &s2));
106        // s3 should be different
107        assert!(!Arc::ptr_eq(&s1, &s3));
108
109        assert_eq!(pool.size(), 2);
110    }
111
112    #[test]
113    fn test_global_pool() {
114        let s1 = intern("global");
115        let s2 = intern("global");
116
117        assert!(Arc::ptr_eq(&s1, &s2));
118    }
119
120    #[test]
121    fn test_clear() {
122        let pool = StringPool::new();
123        let _ = pool.intern("test1");
124        let _ = pool.intern("test2");
125        assert_eq!(pool.size(), 2);
126
127        pool.clear();
128        assert_eq!(pool.size(), 0);
129    }
130}