use std::cell::RefCell;
use std::ffi::c_char;
const SCRATCH_MAX_LEN: usize = 1 << 16;
thread_local! {
static SCRATCH: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
}
fn push_offset(buf: &mut Vec<u8>, s: &str) -> usize {
let start = buf.len();
buf.extend_from_slice(s.as_bytes());
buf.push(0);
start
}
fn maybe_reset(buf: &mut Vec<u8>) {
if buf.len() > SCRATCH_MAX_LEN {
buf.clear();
}
}
pub(crate) fn scratch_txt(s: impl AsRef<str>) -> *const c_char {
SCRATCH.with(|cell| {
let mut buf = cell.borrow_mut();
maybe_reset(&mut buf);
let start = push_offset(&mut buf, s.as_ref());
unsafe { buf.as_ptr().add(start) as *const c_char }
})
}
pub(crate) fn scratch_txt_opt(s: Option<impl AsRef<str>>) -> *const c_char {
match s {
Some(s) => scratch_txt(s),
None => std::ptr::null(),
}
}
pub(crate) fn scratch_txt_two(
a: impl AsRef<str>,
b: impl AsRef<str>,
) -> (*const c_char, *const c_char) {
SCRATCH.with(|cell| {
let mut buf = cell.borrow_mut();
maybe_reset(&mut buf);
let oa = push_offset(&mut buf, a.as_ref());
let ob = push_offset(&mut buf, b.as_ref());
let base = buf.as_ptr();
unsafe { (base.add(oa) as *const c_char, base.add(ob) as *const c_char) }
})
}
pub(crate) fn scratch_txt_three(
a: impl AsRef<str>,
b: impl AsRef<str>,
c: impl AsRef<str>,
) -> (*const c_char, *const c_char, *const c_char) {
SCRATCH.with(|cell| {
let mut buf = cell.borrow_mut();
maybe_reset(&mut buf);
let oa = push_offset(&mut buf, a.as_ref());
let ob = push_offset(&mut buf, b.as_ref());
let oc = push_offset(&mut buf, c.as_ref());
let base = buf.as_ptr();
unsafe {
(
base.add(oa) as *const c_char,
base.add(ob) as *const c_char,
base.add(oc) as *const c_char,
)
}
})
}
pub(crate) fn scratch_txt_slice(items: &[impl AsRef<str>]) -> Vec<*const c_char> {
SCRATCH.with(|cell| {
let mut buf = cell.borrow_mut();
maybe_reset(&mut buf);
let offsets: Vec<usize> = items
.iter()
.map(|s| push_offset(&mut buf, s.as_ref()))
.collect();
let base = buf.as_ptr();
offsets
.into_iter()
.map(|o| unsafe { base.add(o) as *const c_char })
.collect()
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn scratch_txt_null_terminates() {
let p = scratch_txt("hello");
let s = unsafe { CStr::from_ptr(p) };
assert_eq!(s.to_str().unwrap(), "hello");
}
#[test]
fn scratch_txt_opt_none_is_null() {
assert!(scratch_txt_opt(None::<&str>).is_null());
let p = scratch_txt_opt(Some("x"));
assert!(!p.is_null());
assert_eq!(unsafe { CStr::from_ptr(p) }.to_str().unwrap(), "x");
}
#[test]
fn scratch_txt_two_distinct_live_pointers() {
let (a, b) = scratch_txt_two("left", "right");
assert_eq!(unsafe { CStr::from_ptr(a) }.to_str().unwrap(), "left");
assert_eq!(unsafe { CStr::from_ptr(b) }.to_str().unwrap(), "right");
}
#[test]
fn scratch_txt_three_distinct_live_pointers() {
let (a, b, c) = scratch_txt_three("one", "two", "three");
assert_eq!(unsafe { CStr::from_ptr(a) }.to_str().unwrap(), "one");
assert_eq!(unsafe { CStr::from_ptr(b) }.to_str().unwrap(), "two");
assert_eq!(unsafe { CStr::from_ptr(c) }.to_str().unwrap(), "three");
}
#[test]
fn scratch_txt_slice_builds_pointer_array() {
let items = ["a", "bb", "ccc"];
let ptrs = scratch_txt_slice(&items);
assert_eq!(ptrs.len(), 3);
assert_eq!(unsafe { CStr::from_ptr(ptrs[0]) }.to_str().unwrap(), "a");
assert_eq!(unsafe { CStr::from_ptr(ptrs[2]) }.to_str().unwrap(), "ccc");
}
#[test]
fn buffer_resets_when_oversized_but_pointer_still_valid_within_call() {
let big = "z".repeat(8192);
let p = scratch_txt(&big);
assert_eq!(unsafe { CStr::from_ptr(p) }.to_bytes().len(), 8192);
}
}