feat: delete redundant box in cstr_ffi module

This commit is contained in:
2026-07-29 21:48:20 +08:00
parent eba2c8e6e0
commit 708e16a222
+42 -21
View File
@@ -106,11 +106,13 @@ pub type CStrViewVecPtr = *const CStringView;
/// One independent string-vector slot. /// One independent string-vector slot.
/// ///
/// `items` owns the actual `CString` data, each individually boxed so the pointers stay stable /// `items` owns the actual `CString` data. Each `CString`'s backing buffer is an independent heap
/// even when `items` grows. `ptrs` is the parallel pointer array consumed by C/C++; for the /// allocation, so the `CStrPtr` values captured in `ptrs` stay stable even when `items` (or the
/// nullptr-terminated variant a trailing null pointer is appended to `ptrs`. /// containing cache) reallocates and merely moves the `CString` headers. `ptrs` is the parallel
/// pointer array consumed by C/C++; a trailing null pointer is appended for the
/// nullptr-terminated form.
struct CStringVec { struct CStringVec {
items: Vec<Box<CString>>, items: Vec<CString>,
ptrs: Vec<CStrPtr>, ptrs: Vec<CStrPtr>,
} }
@@ -133,9 +135,9 @@ impl CStringVec {
ptrs: Vec::with_capacity(ptrs_cap), ptrs: Vec::with_capacity(ptrs_cap),
}; };
for s in items { for s in items {
let boxed = Box::new(CString::new(*s)?); let cs = CString::new(*s)?;
obj.ptrs.push(boxed.as_ptr()); obj.ptrs.push(cs.as_ptr());
obj.items.push(boxed); obj.items.push(cs);
} }
obj.ptrs.push(std::ptr::null()); obj.ptrs.push(std::ptr::null());
Ok(obj) Ok(obj)
@@ -185,10 +187,29 @@ impl CStringViewVec {
} }
} }
/// The thread-local container that owns every output slot produced during a single FFI call.
///
/// It holds three independent groups of slots, each of which may accumulate an arbitrary number of
/// entries across one call:
///
/// - `strings`: individual NUL-terminated strings produced by [`push_ffi_string`].
/// - `string_vecs`: string-vector slots produced by [`push_ffi_string_vec`] /
/// [`push_ffi_string_vec_with_len`].
/// - `string_view_vecs`: string-view-vector containers produced by
/// [`push_ffi_string_view_vec_with_len`]; only the container is owned here, the viewed string
/// data stays caller-owned.
///
/// # Pointer stability
///
/// The slots are stored directly (not boxed). Each slot's exposed pointer -- a [`CStrPtr`] for a
/// `CString`, the head of `ptrs` for a `CStringVec`, or the head of `views` for a
/// `CStringViewVec` -- addresses an independent heap allocation. Reallocating any of these `Vec`s
/// only moves the owning headers; the heap buffers behind those pointers stay put, so every
/// pointer already handed to C/C++ remains valid until [`StringCache::clear`] is called.
struct StringCache { struct StringCache {
strings: Vec<Box<CString>>, strings: Vec<CString>,
string_vecs: Vec<Box<CStringVec>>, string_vecs: Vec<CStringVec>,
string_view_vecs: Vec<Box<CStringViewVec>>, string_view_vecs: Vec<CStringViewVec>,
} }
impl StringCache { impl StringCache {
@@ -210,20 +231,20 @@ impl StringCache {
self.string_view_vecs.clear(); self.string_view_vecs.clear();
} }
/// Store one boxed NUL-terminated string and return its stable pointer. /// Store one NUL-terminated string and return its stable pointer.
fn push_string(&mut self, s: Box<CString>) -> CStrPtr { fn push_string(&mut self, s: CString) -> CStrPtr {
let ptr = s.as_ptr(); let ptr = s.as_ptr();
self.strings.push(s); self.strings.push(s);
ptr ptr
} }
/// Store one boxed string-vector slot. /// Store one string-vector slot.
fn push_string_vec(&mut self, sv: Box<CStringVec>) { fn push_string_vec(&mut self, sv: CStringVec) {
self.string_vecs.push(sv); self.string_vecs.push(sv);
} }
/// Store one boxed string-view-vector slot. /// Store one string-view-vector slot.
fn push_string_view_vec(&mut self, svv: Box<CStringViewVec>) { fn push_string_view_vec(&mut self, svv: CStringViewVec) {
self.string_view_vecs.push(svv); self.string_view_vecs.push(svv);
} }
} }
@@ -258,8 +279,8 @@ pub fn clear_ffi_strings() {
/// ///
/// Returns an error if `s` contains an interior NUL byte. /// Returns an error if `s` contains an interior NUL byte.
pub fn push_ffi_string(s: &str) -> Result<CStrPtr> { pub fn push_ffi_string(s: &str) -> Result<CStrPtr> {
let boxed = Box::new(CString::new(s)?); let cs = CString::new(s)?;
STRING_CACHE.with(|c| Ok(c.borrow_mut().push_string(boxed))) STRING_CACHE.with(|c| Ok(c.borrow_mut().push_string(cs)))
} }
/// Push one independent string vector (NUL-terminated entries, **nullptr-terminated list**) into /// Push one independent string vector (NUL-terminated entries, **nullptr-terminated list**) into
@@ -276,7 +297,7 @@ pub fn push_ffi_string(s: &str) -> Result<CStrPtr> {
/// ///
/// Returns an error if any entry contains an interior NUL byte. /// Returns an error if any entry contains an interior NUL byte.
pub fn push_ffi_string_vec(items: &[&str]) -> Result<CStrVecPtr> { pub fn push_ffi_string_vec(items: &[&str]) -> Result<CStrVecPtr> {
let sv = Box::new(CStringVec::new(items)?); let sv = CStringVec::new(items)?;
let head = sv.head_ptr(); let head = sv.head_ptr();
STRING_CACHE.with(|c| c.borrow_mut().push_string_vec(sv)); STRING_CACHE.with(|c| c.borrow_mut().push_string_vec(sv));
Ok(head) Ok(head)
@@ -298,7 +319,7 @@ pub fn push_ffi_string_vec(items: &[&str]) -> Result<CStrVecPtr> {
/// ///
/// Returns an error if any entry contains an interior NUL byte. /// Returns an error if any entry contains an interior NUL byte.
pub fn push_ffi_string_vec_with_len(items: &[&str]) -> Result<(CStrVecPtr, usize)> { pub fn push_ffi_string_vec_with_len(items: &[&str]) -> Result<(CStrVecPtr, usize)> {
let sv = Box::new(CStringVec::new(items)?); let sv = CStringVec::new(items)?;
let head = sv.head_ptr(); let head = sv.head_ptr();
let count = sv.item_count(); let count = sv.item_count();
STRING_CACHE.with(|c| c.borrow_mut().push_string_vec(sv)); STRING_CACHE.with(|c| c.borrow_mut().push_string_vec(sv));
@@ -347,7 +368,7 @@ pub fn push_ffi_string_view(s: &str) -> (CStrPtr, usize) {
/// reads (per `Vec::as_ptr` for an empty allocation). The foreign side must drive consumption off /// reads (per `Vec::as_ptr` for an empty allocation). The foreign side must drive consumption off
/// `count` and must not dereference the pointer when `count` is 0. /// `count` and must not dereference the pointer when `count` is 0.
pub fn push_ffi_string_view_vec_with_len(items: &[&str]) -> (CStrViewVecPtr, usize) { pub fn push_ffi_string_view_vec_with_len(items: &[&str]) -> (CStrViewVecPtr, usize) {
let svv = Box::new(CStringViewVec::new(items)); let svv = CStringViewVec::new(items);
let head = svv.head_ptr(); let head = svv.head_ptr();
let count = svv.item_count(); let count = svv.item_count();
STRING_CACHE.with(|c| c.borrow_mut().push_string_view_vec(svv)); STRING_CACHE.with(|c| c.borrow_mut().push_string_view_vec(svv));