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}