triple_r/
string.rs

1use std::{
2    cell::UnsafeCell,
3    marker::PhantomData,
4    ops::{Deref, DerefMut},
5};
6
7/// A wrapper around `String` that allows for reusing its allocation.
8///
9/// This is particularly useful in loops where strings are built and discarded,
10/// as it helps to avoid frequent memory allocations. When the guard returned
11/// by `recycle` is dropped, the `String` is cleared, but its capacity is
12/// retained.
13///
14/// # Examples
15///
16/// ```
17/// use triple_r::ReusableString;
18///
19/// let mut reusable_string = ReusableString::default();
20/// let mut last_capacity = 0;
21///
22/// for i in 0..3 {
23///     let mut string_guard = reusable_string.recycle();
24///     assert!(string_guard.is_empty());
25///     assert_eq!(string_guard.capacity(), last_capacity);
26///
27///     string_guard.push_str("hello world");
28///     last_capacity = string_guard.capacity();
29/// }
30///
31/// let final_guard = reusable_string.recycle();
32/// assert!(final_guard.is_empty());
33/// assert_eq!(final_guard.capacity(), last_capacity);
34/// ```
35#[derive(Debug)]
36pub struct ReusableString {
37    inner: UnsafeCell<String>,
38}
39
40// A `ReusableString` can be sent across threads.
41unsafe impl Send for ReusableString {}
42
43// A `ReusableString` can be shared across threads because the `recycle`
44// method requires `&mut self`, preventing data races.
45unsafe impl Sync for ReusableString {}
46
47impl Default for ReusableString {
48    /// Creates a new, empty `ReusableString`.
49    fn default() -> Self {
50        Self {
51            inner: UnsafeCell::new(String::new()),
52        }
53    }
54}
55
56/// A RAII guard that provides temporary, exclusive access to a `String` from a
57/// [`ReusableString`].
58///
59/// When this guard is dropped, it clears the underlying `String`, preserving its
60/// allocation for future use.
61pub struct ReusableStringGuard<'parent> {
62    inner: *mut String,
63    _parent: PhantomData<&'parent mut ReusableString>,
64}
65
66impl<'parent> Deref for ReusableStringGuard<'parent> {
67    type Target = String;
68
69    /// Provides immutable access to the underlying `String`.
70    fn deref(&self) -> &Self::Target {
71        // SAFETY: `self.inner` is a valid pointer for the lifetime `'parent`.
72        // This is enforced by `_parent` and the `recycle` method signature.
73        unsafe { &*self.inner }
74    }
75}
76
77impl<'parent> DerefMut for ReusableStringGuard<'parent> {
78    /// Provides mutable access to the underlying `String`.
79    fn deref_mut(&mut self) -> &mut Self::Target {
80        // SAFETY: The same guarantees as `deref` apply.
81        unsafe { &mut *self.inner }
82    }
83}
84
85impl ReusableString {
86    /// Reuses the `String`'s allocation, returning a guard for temporary access.
87    ///
88    /// The `&mut self` requirement ensures that only one guard can be active
89    /// at a time.
90    pub fn recycle<'parent>(&'parent mut self) -> ReusableStringGuard<'parent> {
91        // SAFETY: We use `get()` to obtain a raw pointer, which is safe
92        // because `&mut self` guarantees exclusive access.
93        ReusableStringGuard {
94            inner: self.inner.get(),
95            _parent: PhantomData,
96        }
97    }
98}
99
100impl<'parent> Drop for ReusableStringGuard<'parent> {
101    /// Clears the `String` when the guard is dropped.
102    fn drop(&mut self) {
103        // SAFETY: The pointer is guaranteed to be valid for the lifetime
104        // of the guard. Clearing the string prepares it for the next reuse.
105        unsafe {
106            (*self.inner).clear();
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn basic_reuse_works() {
117        let mut s = ReusableString::default();
118        {
119            let mut guard = s.recycle();
120            guard.push_str("hello");
121            assert_eq!(*guard, "hello");
122        }
123        let guard = s.recycle();
124        assert!(guard.is_empty());
125        assert!(guard.capacity() >= 5);
126    }
127
128    #[test]
129    fn capacity_is_preserved() {
130        let mut s = ReusableString::default();
131        let last_capacity;
132        {
133            let mut guard = s.recycle();
134            guard.push_str("some long string to ensure allocation");
135            last_capacity = guard.capacity();
136        }
137        assert!(last_capacity > 0);
138        let guard = s.recycle();
139        assert_eq!(guard.capacity(), last_capacity);
140    }
141
142    #[test]
143    fn empty_reuse_is_still_empty() {
144        let mut s = ReusableString::default();
145        {
146            let _guard = s.recycle();
147        }
148        let guard = s.recycle();
149        assert!(guard.is_empty());
150        assert_eq!(guard.capacity(), 0);
151    }
152}