stack_cstr/
cstr_stack.rs

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