use std::cell::RefCell;
use std::fmt::Write;
thread_local! {
static STRING_POOL: RefCell<StringPool> = RefCell::new(StringPool::new());
}
pub struct StringPool {
buffers: Vec<String>,
max_buffers: usize,
max_buffer_size: usize,
}
impl StringPool {
#[must_use]
pub fn new() -> Self {
Self {
buffers: Vec::with_capacity(8),
max_buffers: 16,
max_buffer_size: 1_048_576, }
}
pub fn acquire(&mut self) -> PooledString {
let buffer = self.buffers.pop().unwrap_or_default();
PooledString { buffer }
}
pub fn release(&mut self, mut buffer: String) {
if self.buffers.len() < self.max_buffers && buffer.capacity() <= self.max_buffer_size {
buffer.clear();
self.buffers.push(buffer);
}
}
}
impl Default for StringPool {
fn default() -> Self {
Self::new()
}
}
pub struct PooledString {
buffer: String,
}
impl PooledString {
#[must_use]
pub fn as_str(&self) -> &str {
&self.buffer
}
#[must_use]
pub fn into_string(mut self) -> String {
std::mem::take(&mut self.buffer)
}
pub fn reserve(&mut self, additional: usize) {
self.buffer.reserve(additional);
}
pub fn push(&mut self, ch: char) {
self.buffer.push(ch);
}
pub fn push_str(&mut self, s: &str) {
self.buffer.push_str(s);
}
pub fn clear(&mut self) {
self.buffer.clear();
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
pub fn extend<I: IntoIterator<Item = char>>(&mut self, iter: I) {
self.buffer.extend(iter);
}
#[must_use]
pub fn ends_with(&self, pat: &str) -> bool {
self.buffer.ends_with(pat)
}
}
impl Write for PooledString {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.buffer.push_str(s);
Ok(())
}
}
impl Drop for PooledString {
fn drop(&mut self) {
STRING_POOL.with(|pool| {
let buffer = std::mem::take(&mut self.buffer);
pool.borrow_mut().release(buffer);
});
}
}
pub fn with_pooled_string<F, R>(f: F) -> R
where
F: FnOnce(&mut PooledString) -> R,
{
STRING_POOL.with(|pool| {
let mut pooled = pool.borrow_mut().acquire();
f(&mut pooled)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_pool_acquire_release() {
let mut pool = StringPool::new();
let mut buffer = pool.acquire();
write!(buffer, "test").unwrap();
assert_eq!(buffer.as_str(), "test");
drop(buffer);
assert_eq!(pool.buffers.len(), 0); }
#[test]
fn test_pooled_string_reuse() {
let result1 = with_pooled_string(|buffer| {
write!(buffer, "first").unwrap();
buffer.as_str().to_string()
});
let result2 = with_pooled_string(|buffer| {
assert_eq!(buffer.as_str(), "");
write!(buffer, "second").unwrap();
buffer.as_str().to_string()
});
assert_eq!(result1, "first");
assert_eq!(result2, "second");
}
#[test]
fn test_pooled_string_write_trait() {
with_pooled_string(|buffer| {
write!(buffer, "Hello").unwrap();
write!(buffer, ", ").unwrap();
write!(buffer, "World!").unwrap();
assert_eq!(buffer.as_str(), "Hello, World!");
});
}
#[test]
fn test_pool_max_buffers() {
let mut pool = StringPool::new();
pool.max_buffers = 2;
for _i in 0..3 {
let mut buffer = pool.acquire();
let string = std::mem::take(&mut buffer.buffer);
pool.release(string);
}
assert!(pool.buffers.len() <= 2);
}
#[test]
fn test_pool_max_buffer_size() {
let mut pool = StringPool::new();
pool.max_buffer_size = 100;
let mut large_buffer = String::with_capacity(200);
large_buffer.push('x');
pool.release(large_buffer);
assert_eq!(pool.buffers.len(), 0);
}
#[test]
fn test_into_string() {
let result = with_pooled_string(|buffer| {
write!(buffer, "test").unwrap();
buffer.as_str().to_string()
});
assert_eq!(result, "test");
}
}