stack_cstr/
cstr_stack.rs

1use std::ffi::{CStr, c_char};
2
3use arrayvec::ArrayString;
4
5use crate::{CStrError, CStrLike};
6
7/// A stack-allocated, null-terminated C string with fixed capacity.
8///
9/// `CStrStack<N>` stores a formatted string directly on the stack, avoiding heap allocation.
10/// The internal buffer has `N` bytes and is always null-terminated (`\0`),
11/// making it safe to pass to C functions via `as_ptr()`.
12///
13/// # Examples
14///
15/// ```
16/// use std::ffi::CStr;
17/// use stack_cstr::{CStrStack, CStrLike};
18///
19/// // Create a stack-allocated C string with capacity for 32 bytes
20/// let s = CStrStack::<32>::new(format_args!("Hello {}", 123)).unwrap();
21///
22/// let cstr: &CStr = s.as_cstr();
23/// assert_eq!(cstr.to_str().unwrap(), "Hello 123");
24///
25/// unsafe {
26///     // FFI-safe pointer
27///     assert_eq!(CStr::from_ptr(s.as_ptr()).to_str().unwrap(), "Hello 123");
28/// }
29/// ```
30///
31/// # Errors
32///
33/// - Returns `Err("buffer overflow")` if the formatted string (plus NUL) does not fit in the buffer.
34/// - Returns `Err("format failed")` if formatting fails (rare case; ArrayString rarely errors).
35pub struct CStrStack<const N: usize> {
36    buf: ArrayString<N>,
37}
38
39impl<const N: usize> CStrStack<N> {
40    /// Creates a new stack-allocated C string using a `format_args!` expression.
41    ///
42    /// The string is written into an internal buffer of size `N`.
43    /// If the string does not fit, returns an error.
44    pub fn new(fmt: std::fmt::Arguments) -> Result<CStrStack<N>, CStrError> {
45        let mut buf: ArrayString<N> = ArrayString::new();
46        std::fmt::write(&mut buf, fmt)?;
47
48        buf.try_push('\0')?;
49
50        Ok(Self { buf })
51    }
52
53    /// Returns a raw pointer to the null-terminated C string.
54    ///
55    /// This pointer is valid for as long as `self` is alive.
56    /// Suitable for passing to FFI.
57    pub fn as_ptr(&self) -> *const c_char {
58        self.buf.as_ptr() as *const c_char
59    }
60
61    /// Returns a reference to the underlying [`CStr`].
62    ///
63    /// # Safety
64    ///
65    /// The buffer is guaranteed to be null-terminated by construction,
66    /// so this is always safe.
67    pub fn as_cstr(&self) -> &CStr {
68        unsafe { CStr::from_bytes_with_nul_unchecked(&self.buf.as_bytes()[..self.buf.len()]) }
69    }
70}
71
72impl<const N: usize> CStrLike for CStrStack<N> {
73    fn as_ptr(&self) -> *const c_char {
74        self.as_ptr()
75    }
76
77    fn as_cstr(&self) -> &CStr {
78        self.as_cstr()
79    }
80}