use std::cell::RefCell;
thread_local! {
static SCRATCH_STRING: RefCell<String> = const { RefCell::new(String::new()) };
static SCRATCH_BYTES: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}
pub fn with_scratch_string<R>(f: impl FnOnce(&mut String) -> R) -> R {
SCRATCH_STRING.with(|cell| {
let mut owned = match cell.try_borrow_mut() {
Ok(mut b) => {
b.clear();
return f(&mut b);
}
Err(_) => String::new(),
};
f(&mut owned)
})
}
pub fn with_scratch_bytes<R>(f: impl FnOnce(&mut Vec<u8>) -> R) -> R {
SCRATCH_BYTES.with(|cell| {
let mut owned = match cell.try_borrow_mut() {
Ok(mut b) => {
b.clear();
return f(&mut b);
}
Err(_) => Vec::new(),
};
f(&mut owned)
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt::Write as _;
#[test]
fn string_scratch_clears_each_call() {
with_scratch_string(|s| write!(s, "first").unwrap());
let observed = with_scratch_string(|s| {
assert!(s.is_empty(), "scratch must be cleared on entry");
write!(s, "second").unwrap();
s.clone()
});
assert_eq!(observed, "second");
}
#[test]
fn bytes_scratch_clears_each_call() {
with_scratch_bytes(|b| b.extend_from_slice(b"first"));
with_scratch_bytes(|b| {
assert!(b.is_empty());
b.extend_from_slice(b"second");
assert_eq!(b.as_slice(), b"second");
});
}
#[test]
fn nested_calls_get_fresh_buffer() {
with_scratch_string(|outer| {
outer.push_str("outer");
let inner = with_scratch_string(|inner| {
assert!(inner.is_empty());
inner.push_str("inner");
inner.clone()
});
assert_eq!(inner, "inner");
assert_eq!(outer.as_str(), "outer");
});
}
}