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}