use std::ffi::{CStr, CString, c_char};
use std::marker::PhantomData;
fn null_terminated_pointers<I>(iter: I) -> impl Iterator<Item = *const c_char>
where
I: IntoIterator,
I::Item: AsRef<CStr>,
{
iter.into_iter()
.map(|s| s.as_ref().as_ptr())
.chain(std::iter::once(std::ptr::null()))
}
trait Sealed {}
#[allow(
clippy::missing_safety_doc,
reason = "users cannot implement sealed traits"
)]
#[expect(private_bounds, reason = "this trait is sealed")]
pub unsafe trait AsCStrArray: Sealed {
fn as_ptr(&self) -> *const *const c_char;
#[inline(always)]
fn as_mut_ptr(&mut self) -> *const *mut c_char {
self.as_ptr().cast()
}
#[must_use]
fn to_vec(&self) -> Vec<CString> {
let mut vec = Vec::new();
let mut ptr = self.as_ptr();
loop {
let c_str_ptr = unsafe { *ptr };
if c_str_ptr.is_null() {
break;
}
let c_str = unsafe { CStr::from_ptr(c_str_ptr) };
vec.push(c_str.to_owned());
ptr = unsafe { ptr.add(1) };
}
vec
}
}
impl<T> Sealed for &T where T: Sealed + ?Sized {}
unsafe impl<T> AsCStrArray for &T
where
T: AsCStrArray + ?Sized,
{
#[inline(always)]
fn as_ptr(&self) -> *const *const c_char {
(self as &T).as_ptr()
}
}
impl<T> Sealed for &mut T where T: Sealed + ?Sized {}
unsafe impl<T> AsCStrArray for &mut T
where
T: AsCStrArray + ?Sized,
{
#[inline(always)]
fn as_ptr(&self) -> *const *const c_char {
(self as &T).as_ptr()
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct CStrPtr(*const *const c_char);
impl CStrPtr {
#[must_use]
pub unsafe fn new(ptr: *const *const c_char) -> Self {
Self(ptr)
}
}
impl Sealed for CStrPtr {}
unsafe impl AsCStrArray for CStrPtr {
#[inline(always)]
fn as_ptr(&self) -> *const *const c_char {
self.0
}
}
#[derive(Clone, Debug)]
pub struct BorrowedCStrs<'a> {
pointers: Vec<*const c_char>,
phantom: PhantomData<Vec<&'a CStr>>,
}
impl<'a> Sealed for BorrowedCStrs<'a> {}
unsafe impl<'a> AsCStrArray for BorrowedCStrs<'a> {
#[inline(always)]
fn as_ptr(&self) -> *const *const c_char {
self.pointers.as_ptr()
}
}
impl<'a, T> FromIterator<&'a T> for BorrowedCStrs<'a>
where
T: AsRef<CStr> + ?Sized,
{
#[inline(always)]
fn from_iter<I: IntoIterator<Item = &'a T>>(iter: I) -> Self {
let pointers = null_terminated_pointers(iter).collect();
unsafe { Self::from_vec(pointers) }
}
}
impl BorrowedCStrs<'_> {
#[inline(always)]
#[must_use]
pub unsafe fn from_vec(pointers: Vec<*const c_char>) -> Self {
Self {
pointers,
phantom: PhantomData,
}
}
#[inline(always)]
#[must_use]
pub fn into_vec(self) -> Vec<*const c_char> {
self.pointers
}
}
impl From<BorrowedCStrs<'_>> for Vec<*const c_char> {
#[inline(always)]
fn from(c_str_vec: BorrowedCStrs) -> Self {
c_str_vec.into_vec()
}
}
#[derive(Debug)]
pub struct OwnedCStrs {
pointers: Vec<*const c_char>,
values: Vec<CString>,
}
impl Sealed for OwnedCStrs {}
unsafe impl AsCStrArray for OwnedCStrs {
#[inline(always)]
fn as_ptr(&self) -> *const *const c_char {
self.pointers.as_ptr()
}
}
impl OwnedCStrs {
#[must_use]
pub fn new(values: Vec<CString>) -> Self {
let pointers = null_terminated_pointers(&values).collect();
unsafe { Self::from_pointers_and_values(pointers, values) }
}
#[inline(always)]
#[must_use]
pub unsafe fn from_pointers_and_values(
pointers: Vec<*const c_char>,
values: Vec<CString>,
) -> Self {
Self { pointers, values }
}
#[inline(always)]
#[must_use]
pub fn into_pointers_and_values(self) -> (Vec<*const c_char>, Vec<CString>) {
(self.pointers, self.values)
}
}
impl Clone for OwnedCStrs {
fn clone(&self) -> Self {
let strings = self.values.clone();
Self::new(strings)
}
fn clone_from(&mut self, source: &Self) {
self.values.clone_from(&source.values);
self.pointers.clear();
self.pointers.extend(null_terminated_pointers(&self.values));
}
}
impl From<Vec<CString>> for OwnedCStrs {
#[inline(always)]
fn from(values: Vec<CString>) -> Self {
Self::new(values)
}
}
impl<A> FromIterator<A> for OwnedCStrs
where
A: Into<CString>,
{
fn from_iter<I: IntoIterator<Item = A>>(iter: I) -> Self {
let values = iter.into_iter().map(Into::into).collect();
Self::new(values)
}
}
#[expect(private_bounds, reason = "this trait is sealed")]
pub trait IntoCStrArray: Sealed {
type CStrArray: AsCStrArray;
#[must_use]
fn into_c_str_array(self) -> Self::CStrArray;
}
impl<T: AsCStrArray> IntoCStrArray for T {
type CStrArray = Self;
#[inline(always)]
fn into_c_str_array(self) -> Self::CStrArray {
self
}
}
impl<T> Sealed for &[T] where T: AsRef<CStr> {}
impl<'a, T> IntoCStrArray for &'a [T]
where
T: AsRef<CStr>,
{
type CStrArray = BorrowedCStrs<'a>;
#[inline(always)]
fn into_c_str_array(self) -> BorrowedCStrs<'a> {
BorrowedCStrs::from_iter(self)
}
}
impl<T> Sealed for Vec<T> where T: Into<CString> {}
impl<T> IntoCStrArray for Vec<T>
where
T: Into<CString>,
{
type CStrArray = OwnedCStrs;
#[inline(always)]
fn into_c_str_array(self) -> OwnedCStrs {
OwnedCStrs::from_iter(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_c_strings(words: &[&str]) -> Vec<CString> {
words.iter().map(|s| CString::new(*s).unwrap()).collect()
}
#[test]
fn as_c_str_array_to_vec() {
let strings = make_c_strings(&["foo", "bar", "baz"]);
let owned = OwnedCStrs::new(strings.clone());
assert_eq!(owned.to_vec(), strings);
let empty = OwnedCStrs::new(vec![]);
assert_eq!(empty.to_vec(), Vec::<CString>::new());
}
#[test]
fn borrowed_c_strs_from_iterator() {
let strings = make_c_strings(&["alpha", "beta"]);
let borrowed: BorrowedCStrs = strings.iter().collect();
assert_eq!(borrowed.to_vec(), strings);
let ptr = borrowed.as_ptr();
unsafe {
assert_eq!(*ptr, strings[0].as_ptr());
assert_eq!(*ptr.add(1), strings[1].as_ptr());
assert!((*ptr.add(2)).is_null());
}
}
#[test]
fn owned_c_strs_new() {
let strings = make_c_strings(&["hello", "world"]);
let owned = OwnedCStrs::new(strings.clone());
assert_eq!(owned.to_vec(), strings);
let (pointers, values) = owned.into_pointers_and_values();
for (i, value) in values.iter().enumerate() {
assert_eq!(pointers[i], value.as_ptr());
}
assert!(pointers[values.len()].is_null());
}
#[test]
fn owned_c_strs_clone() {
let strings = make_c_strings(&["foo", "bar"]);
let owned = OwnedCStrs::new(strings.clone());
let cloned = owned.clone();
assert_eq!(cloned.to_vec(), strings);
let orig_ptr = owned.as_ptr();
let clone_ptr = cloned.as_ptr();
unsafe {
assert_ne!(orig_ptr, clone_ptr);
assert_ne!(*orig_ptr, *clone_ptr);
assert_ne!(*orig_ptr.add(1), *clone_ptr.add(1));
}
}
#[test]
fn owned_c_strs_clone_from() {
let strings1 = make_c_strings(&["one"]);
let strings2 = make_c_strings(&["two", "three"]);
let source = OwnedCStrs::new(strings2.clone());
let mut dest = OwnedCStrs::new(strings1);
dest.clone_from(&source);
assert_eq!(dest.to_vec(), strings2);
let (pointers, values) = dest.into_pointers_and_values();
for (i, value) in values.iter().enumerate() {
assert_eq!(pointers[i], value.as_ptr());
}
assert!(pointers[values.len()].is_null());
}
#[test]
fn owned_c_strs_from_iterator() {
let strings = make_c_strings(&["x", "y", "z"]);
let owned: OwnedCStrs = strings.iter().cloned().collect();
assert_eq!(owned.to_vec(), strings);
let (pointers, values) = owned.into_pointers_and_values();
for (i, value) in values.iter().enumerate() {
assert_eq!(pointers[i], value.as_ptr());
}
assert!(pointers[values.len()].is_null());
}
}