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}