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 {}