cstring_array/
array.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2//
3// SPDX-License-Identifier: MIT
4
5use std::{
6    ffi::{CString, c_char},
7    ptr::null,
8    slice::Iter
9};
10
11use crate::error::{CStringArrayError, CStringArrayError::EmptyArray};
12
13/// Safe wrapper for passing string arrays to C FFI as `char**`.
14///
15/// This structure provides a safe abstraction over the common C pattern of
16/// null-terminated arrays of strings (`char**`), often used for command-line
17/// arguments (`argv`).
18///
19/// # Memory Safety
20///
21/// - Guarantees proper memory layout compatible with C's `char**`
22/// - Automatically manages lifetime of all C strings
23/// - Ensures null-termination of the pointer array
24/// - Prevents dangling pointers through RAII
25/// - Zero-copy when constructed from `Vec<CString>`
26///
27/// # Example
28///
29/// ```
30/// use std::ffi::c_char;
31///
32/// use cstring_array::CStringArray;
33///
34/// let args = vec![
35///     "program".to_string(),
36///     "--verbose".to_string(),
37///     "file.txt".to_string(),
38/// ];
39/// let array = CStringArray::new(args).unwrap();
40///
41/// // Safe to pass to C FFI
42/// let ptr: *const *const c_char = array.as_ptr();
43/// assert_eq!(array.len(), 3);
44/// ```
45#[derive(Debug)]
46pub struct CStringArray {
47    strings:  Vec<CString>,
48    pointers: Vec<*const c_char>
49}
50
51impl CStringArray {
52    /// Creates a new `CStringArray` from a vector of strings.
53    ///
54    /// # Arguments
55    ///
56    /// * `strings` - Vector of strings to convert into C-compatible format
57    ///
58    /// # Errors
59    ///
60    /// Returns `CStringArrayError::NulError` if any string contains an interior
61    /// null byte. Returns `CStringArrayError::EmptyArray` if the input
62    /// vector is empty.
63    ///
64    /// # Example
65    ///
66    /// ```
67    /// use cstring_array::CStringArray;
68    ///
69    /// let args = vec!["foo".to_string(), "bar".to_string()];
70    /// let array = CStringArray::new(args).unwrap();
71    /// assert_eq!(array.len(), 2);
72    /// ```
73    pub fn new(strings: Vec<String>) -> Result<Self, CStringArrayError> {
74        if strings.is_empty() {
75            return Err(EmptyArray);
76        }
77
78        let cstrings: Vec<CString> = strings
79            .into_iter()
80            .map(CString::new)
81            .collect::<Result<_, _>>()?;
82
83        let mut pointers: Vec<*const c_char> = Vec::with_capacity(cstrings.len() + 1);
84        pointers.extend(cstrings.iter().map(|s| s.as_ptr()));
85        pointers.push(null());
86
87        Ok(Self {
88            strings: cstrings,
89            pointers
90        })
91    }
92
93    /// Creates a new `CStringArray` from a vector of `CString`s (zero-copy).
94    ///
95    /// This is the most efficient constructor as it takes ownership of
96    /// already-allocated `CString` instances without re-allocation.
97    ///
98    /// # Arguments
99    ///
100    /// * `strings` - Vector of `CString` instances
101    ///
102    /// # Errors
103    ///
104    /// Returns `CStringArrayError::EmptyArray` if the input vector is empty.
105    ///
106    /// # Example
107    ///
108    /// ```
109    /// use std::ffi::CString;
110    ///
111    /// use cstring_array::CStringArray;
112    ///
113    /// let cstrings = vec![
114    ///     CString::new("hello").unwrap(),
115    ///     CString::new("world").unwrap(),
116    /// ];
117    /// let array = CStringArray::from_cstrings(cstrings).unwrap();
118    /// assert_eq!(array.len(), 2);
119    /// ```
120    pub fn from_cstrings(strings: Vec<CString>) -> Result<Self, CStringArrayError> {
121        if strings.is_empty() {
122            return Err(EmptyArray);
123        }
124
125        let mut pointers: Vec<*const c_char> = Vec::with_capacity(strings.len() + 1);
126        pointers.extend(strings.iter().map(|s| s.as_ptr()));
127        pointers.push(null());
128
129        Ok(Self {
130            strings,
131            pointers
132        })
133    }
134
135    /// Returns a pointer suitable for passing to C functions expecting
136    /// `char**`.
137    ///
138    /// The returned pointer is valid for the lifetime of this `CStringArray`.
139    /// The pointer array is null-terminated as required by C conventions.
140    ///
141    /// # Safety
142    ///
143    /// The caller must ensure that:
144    /// - The pointer is not used after this `CStringArray` is dropped
145    /// - The pointer is not used to modify the strings (use `as_mut_ptr` for
146    ///   that)
147    ///
148    /// # Example
149    ///
150    /// ```
151    /// use std::ffi::c_char;
152    ///
153    /// use cstring_array::CStringArray;
154    ///
155    /// let array = CStringArray::new(vec!["test".to_string()]).unwrap();
156    /// let ptr: *const *const c_char = array.as_ptr();
157    ///
158    /// // Safe to pass to C FFI functions like execve, etc.
159    /// ```
160    #[inline]
161    #[must_use]
162    pub fn as_ptr(&self) -> *const *const c_char {
163        self.pointers.as_ptr()
164    }
165
166    /// Returns a mutable pointer suitable for C functions expecting `char**`.
167    ///
168    /// # Safety
169    ///
170    /// The caller must ensure that:
171    /// - The pointer is not used after this `CStringArray` is dropped
172    /// - C code does not replace pointers in the array (undefined behavior)
173    /// - C code only modifies string contents, not pointer values
174    ///
175    /// # Example
176    ///
177    /// ```
178    /// use std::ffi::c_char;
179    ///
180    /// use cstring_array::CStringArray;
181    ///
182    /// let mut array = CStringArray::new(vec!["test".to_string()]).unwrap();
183    /// let ptr: *mut *const c_char = array.as_mut_ptr();
184    /// ```
185    #[inline]
186    #[must_use]
187    pub fn as_mut_ptr(&mut self) -> *mut *const c_char {
188        self.pointers.as_mut_ptr()
189    }
190
191    /// Returns the number of strings in the array.
192    ///
193    /// This count does not include the null terminator.
194    ///
195    /// # Example
196    ///
197    /// ```
198    /// use cstring_array::CStringArray;
199    ///
200    /// let array = CStringArray::new(vec!["a".to_string(), "b".to_string()]).unwrap();
201    /// assert_eq!(array.len(), 2);
202    /// ```
203    #[inline]
204    #[must_use]
205    pub fn len(&self) -> usize {
206        self.strings.len()
207    }
208
209    /// Returns `true` if the array contains no strings.
210    ///
211    /// Note: Due to the constructor constraints, this will always return
212    /// `false` for successfully constructed instances, but is provided for
213    /// completeness.
214    ///
215    /// # Example
216    ///
217    /// ```
218    /// use cstring_array::CStringArray;
219    ///
220    /// let array = CStringArray::new(vec!["x".to_string()]).unwrap();
221    /// assert!(!array.is_empty());
222    /// ```
223    #[inline]
224    #[must_use]
225    pub fn is_empty(&self) -> bool {
226        self.strings.is_empty()
227    }
228
229    /// Returns a reference to the underlying `CString` at the specified index.
230    ///
231    /// # Arguments
232    ///
233    /// * `index` - The index of the string to retrieve
234    ///
235    /// # Returns
236    ///
237    /// Returns `Some(&CString)` if the index is valid, `None` otherwise.
238    ///
239    /// # Example
240    ///
241    /// ```
242    /// use cstring_array::CStringArray;
243    ///
244    /// let array = CStringArray::new(vec!["first".to_string(), "second".to_string()]).unwrap();
245    /// assert_eq!(array.get(0).unwrap().to_str().unwrap(), "first");
246    /// assert_eq!(array.get(1).unwrap().to_str().unwrap(), "second");
247    /// assert!(array.get(2).is_none());
248    /// ```
249    #[inline]
250    #[must_use]
251    pub fn get(&self, index: usize) -> Option<&CString> {
252        self.strings.get(index)
253    }
254
255    /// Returns an iterator over the `CString` references.
256    ///
257    /// # Example
258    ///
259    /// ```
260    /// use cstring_array::CStringArray;
261    ///
262    /// let array = CStringArray::new(vec!["a".to_string(), "b".to_string()]).unwrap();
263    /// let strings: Vec<_> = array.iter().collect();
264    /// assert_eq!(strings.len(), 2);
265    /// ```
266    #[inline]
267    pub fn iter(&self) -> Iter<'_, CString> {
268        self.strings.iter()
269    }
270
271    /// Returns a slice of the underlying `CString` array.
272    ///
273    /// # Example
274    ///
275    /// ```
276    /// use cstring_array::CStringArray;
277    ///
278    /// let array = CStringArray::new(vec!["a".to_string()]).unwrap();
279    /// let slice = array.as_slice();
280    /// assert_eq!(slice.len(), 1);
281    /// ```
282    #[inline]
283    #[must_use]
284    pub fn as_slice(&self) -> &[CString] {
285        &self.strings
286    }
287
288    /// Consumes the array and returns the underlying vector of `CString`s.
289    ///
290    /// # Example
291    ///
292    /// ```
293    /// use cstring_array::CStringArray;
294    ///
295    /// let array = CStringArray::new(vec!["test".to_string()]).unwrap();
296    /// let strings = array.into_strings();
297    /// assert_eq!(strings.len(), 1);
298    /// ```
299    #[inline]
300    #[must_use]
301    pub fn into_strings(mut self) -> Vec<CString> {
302        std::mem::take(&mut self.strings)
303    }
304}
305
306impl Drop for CStringArray {
307    fn drop(&mut self) {
308        self.pointers.clear();
309    }
310}
311
312unsafe impl Send for CStringArray {}
313unsafe impl Sync for CStringArray {}