use fish_common::{assert_send, assert_sync};
use std::ffi::{c_char, CStr, CString};
use std::marker::PhantomData;
use std::pin::Pin;
use std::ptr;
struct NullTerminatedArray<'p> {
pointers: Box<[*const c_char]>,
_phantom: PhantomData<&'p CStr>,
}
impl<'p> NullTerminatedArray<'p> {
pub fn get(&self) -> *mut *const c_char {
assert!(
!self.pointers.is_empty() && self.pointers.last().unwrap().is_null(),
"Should have null terminator"
);
self.pointers.as_ptr().cast_mut()
}
pub fn new<S: AsRef<CStr>>(strs: &'p [S]) -> Self {
let mut pointers = Vec::new();
pointers.reserve_exact(1 + strs.len());
for s in strs {
pointers.push(s.as_ref().as_ptr());
}
pointers.push(ptr::null());
NullTerminatedArray {
pointers: pointers.into_boxed_slice(),
_phantom: PhantomData,
}
}
}
unsafe impl Send for NullTerminatedArray<'_> {}
unsafe impl Sync for NullTerminatedArray<'_> {}
pub struct OwningNullTerminatedArray {
strings: Pin<Box<[CString]>>,
null_terminated_array: NullTerminatedArray<'static>,
}
const _: () = assert_send::<OwningNullTerminatedArray>();
const _: () = assert_sync::<OwningNullTerminatedArray>();
impl OwningNullTerminatedArray {
pub fn get(&self) -> *mut *const c_char {
self.null_terminated_array.get()
}
pub fn get_mut(&self) -> *mut *mut c_char {
self.get().cast()
}
pub fn new(strs: Vec<CString>) -> Self {
let strings = strs.into_boxed_slice();
let string_slice: &'static [CString] = unsafe { std::mem::transmute(&*strings) };
OwningNullTerminatedArray {
strings: Pin::from(strings),
null_terminated_array: NullTerminatedArray::new(string_slice),
}
}
pub fn len(&self) -> usize {
self.strings.len()
}
pub fn iter(&self) -> impl Iterator<Item = &CString> {
self.strings.iter()
}
}
#[cfg(test)]
mod tests {
use super::{NullTerminatedArray, OwningNullTerminatedArray};
use std::ffi::{CStr, CString};
use std::ptr;
#[test]
fn test_null_terminated_array() {
let owned_strs = &[CString::new("foo").unwrap(), CString::new("bar").unwrap()];
let strs = owned_strs.iter().map(|s| s.as_c_str()).collect::<Vec<_>>();
let arr = NullTerminatedArray::new(&strs);
let ptr = arr.get();
unsafe {
assert_eq!(CStr::from_ptr(*ptr).to_str().unwrap(), "foo");
assert_eq!(CStr::from_ptr(*ptr.add(1)).to_str().unwrap(), "bar");
assert_eq!(*ptr.add(2), ptr::null());
}
}
#[test]
fn test_owning_null_terminated_array() {
let owned_strs = vec![CString::new("foo").unwrap(), CString::new("bar").unwrap()];
let arr = OwningNullTerminatedArray::new(owned_strs);
let ptr = arr.get();
unsafe {
assert_eq!(CStr::from_ptr(*ptr).to_str().unwrap(), "foo");
assert_eq!(CStr::from_ptr(*ptr.add(1)).to_str().unwrap(), "bar");
assert_eq!(*ptr.add(2), ptr::null());
}
assert_eq!(arr.len(), 2);
let mut iter = arr.iter();
assert_eq!(iter.next().map(|s| s.to_str().unwrap()), Some("foo"));
assert_eq!(iter.next().map(|s| s.to_str().unwrap()), Some("bar"));
assert_eq!(iter.next(), None);
}
}