diff --git a/omrf/src/cstr_ffi.rs b/omrf/src/cstr_ffi.rs index 1b6e0e8..93fabf5 100644 --- a/omrf/src/cstr_ffi.rs +++ b/omrf/src/cstr_ffi.rs @@ -106,11 +106,13 @@ pub type CStrViewVecPtr = *const CStringView; /// One independent string-vector slot. /// -/// `items` owns the actual `CString` data, each individually boxed so the pointers stay stable -/// even when `items` grows. `ptrs` is the parallel pointer array consumed by C/C++; for the -/// nullptr-terminated variant a trailing null pointer is appended to `ptrs`. +/// `items` owns the actual `CString` data. Each `CString`'s backing buffer is an independent heap +/// allocation, so the `CStrPtr` values captured in `ptrs` stay stable even when `items` (or the +/// 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 { - items: Vec>, + items: Vec, ptrs: Vec, } @@ -133,9 +135,9 @@ impl CStringVec { ptrs: Vec::with_capacity(ptrs_cap), }; for s in items { - let boxed = Box::new(CString::new(*s)?); - obj.ptrs.push(boxed.as_ptr()); - obj.items.push(boxed); + let cs = CString::new(*s)?; + obj.ptrs.push(cs.as_ptr()); + obj.items.push(cs); } obj.ptrs.push(std::ptr::null()); 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 { - strings: Vec>, - string_vecs: Vec>, - string_view_vecs: Vec>, + strings: Vec, + string_vecs: Vec, + string_view_vecs: Vec, } impl StringCache { @@ -210,20 +231,20 @@ impl StringCache { self.string_view_vecs.clear(); } - /// Store one boxed NUL-terminated string and return its stable pointer. - fn push_string(&mut self, s: Box) -> CStrPtr { + /// Store one NUL-terminated string and return its stable pointer. + fn push_string(&mut self, s: CString) -> CStrPtr { let ptr = s.as_ptr(); self.strings.push(s); ptr } - /// Store one boxed string-vector slot. - fn push_string_vec(&mut self, sv: Box) { + /// Store one string-vector slot. + fn push_string_vec(&mut self, sv: CStringVec) { self.string_vecs.push(sv); } - /// Store one boxed string-view-vector slot. - fn push_string_view_vec(&mut self, svv: Box) { + /// Store one string-view-vector slot. + fn push_string_view_vec(&mut self, svv: CStringViewVec) { 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. pub fn push_ffi_string(s: &str) -> Result { - let boxed = Box::new(CString::new(s)?); - STRING_CACHE.with(|c| Ok(c.borrow_mut().push_string(boxed))) + let cs = CString::new(s)?; + STRING_CACHE.with(|c| Ok(c.borrow_mut().push_string(cs))) } /// Push one independent string vector (NUL-terminated entries, **nullptr-terminated list**) into @@ -276,7 +297,7 @@ pub fn push_ffi_string(s: &str) -> Result { /// /// Returns an error if any entry contains an interior NUL byte. pub fn push_ffi_string_vec(items: &[&str]) -> Result { - let sv = Box::new(CStringVec::new(items)?); + let sv = CStringVec::new(items)?; let head = sv.head_ptr(); STRING_CACHE.with(|c| c.borrow_mut().push_string_vec(sv)); Ok(head) @@ -298,7 +319,7 @@ pub fn push_ffi_string_vec(items: &[&str]) -> Result { /// /// Returns an error if any entry contains an interior NUL byte. 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 count = sv.item_count(); 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 /// `count` and must not dereference the pointer when `count` is 0. 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 count = svv.item_count(); STRING_CACHE.with(|c| c.borrow_mut().push_string_view_vec(svv));