Skip to content

Commit 2bac408

Browse files
committed
docs: Improve documentation comments and add usage examples
1 parent 286d16e commit 2bac408

File tree

7 files changed

+293
-2
lines changed

7 files changed

+293
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "stack-cstr"
3-
version = "0.1.0"
3+
version = "0.1.1"
44
edition = "2024"
55
authors = ["Junkang Yuan <[email protected]>"]
66
description = "High-performance stack-to-heap C string creation for Rust with FFI support"

src/cstr_heap.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,60 @@ use std::ffi::{CStr, CString, c_char};
22

33
use crate::CStrLike;
44

5+
/// A heap-allocated C-compatible string wrapper.
6+
///
7+
/// `CStrHeap` owns a [`CString`] internally, and implements the
8+
/// [`CStrLike`] trait to provide a uniform interface with
9+
/// stack-allocated alternatives (e.g. `CStrStack`).
10+
///
11+
/// This is used as a fallback when the string cannot fit into
12+
/// a fixed-size stack buffer.
513
pub struct CStrHeap {
614
cstr: CString,
715
}
816

917
impl CStrHeap {
18+
/// Creates a new `CStrHeap` from a given [`CString`].
19+
///
20+
/// # Arguments
21+
///
22+
/// * `s` - A [`CString`] instance to be wrapped.
23+
///
24+
/// # Examples
25+
///
26+
/// ```
27+
/// use std::ffi::CString;
28+
/// use stack_cstr::CStrHeap;
29+
///
30+
/// let cstr = CString::new("hello").unwrap();
31+
/// let heap = CStrHeap::new(cstr);
32+
/// assert_eq!(unsafe { std::ffi::CStr::from_ptr(heap.as_ptr()) }.to_str().unwrap(), "hello");
33+
/// ```
1034
pub fn new(s: CString) -> Self {
1135
Self { cstr: s }
1236
}
1337

38+
/// Returns a raw pointer to the underlying C string.
39+
///
40+
/// The pointer is guaranteed to be valid as long as the `CStrHeap`
41+
/// instance is alive. The string is null-terminated.
1442
pub fn as_ptr(&self) -> *const c_char {
1543
self.cstr.as_ptr()
1644
}
1745

46+
/// Returns a reference to the underlying [`CStr`].
47+
///
48+
/// # Examples
49+
///
50+
/// ```
51+
/// use std::ffi::CString;
52+
/// use stack_cstr::CStrHeap;
53+
///
54+
/// let cstr = CString::new("hello").unwrap();
55+
/// let heap = CStrHeap::new(cstr);
56+
/// let slice = heap.as_cstr();
57+
/// assert_eq!(slice.to_str().unwrap(), "hello");
58+
/// ```
1859
pub fn as_cstr(&self) -> &CStr {
1960
self.cstr.as_c_str()
2061
}

src/cstr_like.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,44 @@
11
use std::ffi::{CStr, c_char};
22

3+
/// A common interface for C-compatible string types used in this crate.
4+
///
5+
/// `CStrLike` abstracts over different string storage strategies
6+
/// (e.g. stack-allocated and heap-allocated C strings) so they can
7+
/// be used interchangeably.
8+
///
9+
/// Types that implement this trait guarantee:
10+
/// - The returned pointer from [`as_ptr`] is a valid, null-terminated
11+
/// C string for as long as the implementor is alive.
12+
/// - The returned [`CStr`] reference from [`as_cstr`] is always valid.
13+
///
14+
/// This trait is mainly intended to unify [`CStrHeap`] (heap-allocated)
15+
/// and `CStrStack<N>` (stack-allocated with a fixed buffer).
16+
///
17+
/// # Examples
18+
///
19+
/// ```
20+
/// use std::ffi::{CString, CStr};
21+
/// use stack_cstr::{CStrHeap, CStrLike};
22+
///
23+
/// let cstr = CString::new("hello").unwrap();
24+
/// let heap = CStrHeap::new(cstr);
25+
///
26+
/// // Use the trait methods
27+
/// let ptr = heap.as_ptr();
28+
/// let slice: &CStr = heap.as_cstr();
29+
///
30+
/// assert_eq!(slice.to_str().unwrap(), "hello");
31+
/// unsafe {
32+
/// assert_eq!(CStr::from_ptr(ptr).to_str().unwrap(), "hello");
33+
/// }
34+
/// ```
335
pub trait CStrLike {
36+
/// Returns a raw pointer to the null-terminated C string.
37+
///
38+
/// The pointer is valid as long as `self` is alive.
39+
/// This is mainly intended for FFI calls.
440
fn as_ptr(&self) -> *const c_char;
41+
42+
/// Returns a reference to the underlying [`CStr`].
543
fn as_cstr(&self) -> &CStr;
644
}

src/cstr_stack.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,51 @@ use arrayvec::ArrayString;
44

55
use crate::CStrLike;
66

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).
742
pub struct CStrStack<const N: usize> {
843
buf: [u8; N],
944
len: usize,
1045
}
1146

1247
impl<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.
1352
pub fn new(fmt: std::fmt::Arguments) -> Result<CStrStack<N>, &'static str> {
1453
let mut buf: ArrayString<N> = ArrayString::new();
1554
std::fmt::write(&mut buf, fmt).map_err(|_| "format failed")?;
@@ -29,10 +68,20 @@ impl<const N: usize> CStrStack<N> {
2968
})
3069
}
3170

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.
3275
pub fn as_ptr(&self) -> *const c_char {
3376
self.buf.as_ptr() as *const c_char
3477
}
3578

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.
3685
pub fn as_cstr(&self) -> &CStr {
3786
unsafe { CStr::from_bytes_with_nul_unchecked(&self.buf[..self.len + 1]) }
3887
}

src/lib.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,104 @@
1+
//! # stack_cstr
2+
//!
3+
//! `stack_cstr` provides ergonomic and efficient ways to create
4+
//! [`CStr`](std::ffi::CStr) values for FFI interoperability.
5+
//!
6+
//! The crate offers both **stack-based** and **heap-based** strategies
7+
//! for storing C-compatible strings. Its goal is to minimize heap
8+
//! allocations for short-lived or short strings while providing
9+
//! automatic fallback to the heap when needed.
10+
//!
11+
//! ## Motivation
12+
//!
13+
//! When interacting with C APIs, strings must be passed as
14+
//! null-terminated `*const c_char`. The standard way in Rust
15+
//! is to use [`CString`], which always allocates on the heap.
16+
//!
17+
//! However, in performance-sensitive or embedded environments,
18+
//! frequent heap allocations are undesirable. `stack_cstr` allows
19+
//! you to create `CStr` objects backed by **fixed-size stack buffers**,
20+
//! avoiding heap allocations for common short strings.
21+
//!
22+
//! ## Core Components
23+
//!
24+
//! - [`CStrLike`](crate::CStrLike): A trait for types that can expose
25+
//! a `*const c_char` and a `&CStr`.
26+
//! - [`CStrStack`](crate::CStrStack): A stack-allocated C string with
27+
//! a user-defined capacity.
28+
//! - [`CStrHeap`](crate::CStrHeap): A heap-allocated C string wrapper
29+
//! around [`CString`].
30+
//! - [`cstr!`](crate::cstr): A macro that automatically chooses between
31+
//! stack or heap storage depending on the string length.
32+
//!
33+
//! ## Example: Using the `cstr!` Macro
34+
//!
35+
//! ```
36+
//! use std::ffi::CStr;
37+
//! use stack_cstr::{cstr, CStrLike};
38+
//!
39+
//! // Default stack sizes are [32, 128]
40+
//! let s = cstr!("Hello {}", 42);
41+
//! assert_eq!(s.as_cstr().to_str().unwrap(), "Hello 42");
42+
//!
43+
//! // Explicitly set candidate stack buffer sizes
44+
//! let s = cstr!([16, 64], "Pi = {:.2}", 3.14159);
45+
//! assert_eq!(s.as_cstr().to_str().unwrap(), "Pi = 3.14");
46+
//!
47+
//! unsafe {
48+
//! // Pass to FFI as *const c_char
49+
//! let ptr = s.as_ptr();
50+
//! assert_eq!(CStr::from_ptr(ptr).to_str().unwrap(), "Pi = 3.14");
51+
//! }
52+
//! ```
53+
//!
54+
//! ## Example: Manual Use of `CStrStack`
55+
//!
56+
//! ```
57+
//! use stack_cstr::CStrStack;
58+
//!
59+
//! // Create a stack-allocated C string with capacity 32
60+
//! let s = CStrStack::<32>::new(format_args!("ABC {}!", 123)).unwrap();
61+
//! assert_eq!(s.as_cstr().to_str().unwrap(), "ABC 123!");
62+
//! ```
63+
//!
64+
//! ## Example: Manual Use of `CStrHeap`
65+
//!
66+
//! ```
67+
//! use std::ffi::CString;
68+
//! use stack_cstr::CStrHeap;
69+
//!
70+
//! let heap_str = CStrHeap::new(CString::new("Hello from heap").unwrap());
71+
//! assert_eq!(heap_str.as_cstr().to_str().unwrap(), "Hello from heap");
72+
//! ```
73+
//!
74+
//! ## Design Notes
75+
//!
76+
//! - `CStrStack` checks buffer boundaries at construction time and
77+
//! guarantees proper null termination.
78+
//! - `cstr!` hides the complexity by trying multiple stack sizes and
79+
//! falling back to heap when necessary.
80+
//! - Returned values from `cstr!` are `Box<dyn CStrLike>` for uniformity,
81+
//! even when stack-backed. This introduces a single heap allocation
82+
//! for type erasure, which is usually negligible compared to string allocation.
83+
//!
84+
//! ## Use Cases
85+
//!
86+
//! - Embedding short strings in FFI calls without heap allocation.
87+
//! - Performance-sensitive applications where allocation patterns matter.
88+
//! - Embedded systems where heap memory is constrained.
89+
//!
90+
//! ## Limitations
91+
//!
92+
//! - `CStrStack` requires a compile-time constant buffer size.
93+
//! - Even stack-backed strings returned from `cstr!` are wrapped in a `Box`
94+
//! to allow dynamic dispatch.
95+
//!
96+
//! ## See Also
97+
//!
98+
//! - [`CString`](std::ffi::CString) for heap-allocated C strings
99+
//! - [`CStr`](std::ffi::CStr) for borrowed C strings
100+
//! - [`arrayvec`](https://docs.rs/arrayvec) used internally for formatting
101+
1102
pub mod cstr_heap;
2103
pub mod cstr_like;
3104
pub mod cstr_stack;

src/macros.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,65 @@
1+
/// A macro to create a C-compatible string (`&CStr`) with stack allocation fallback.
2+
///
3+
/// The `cstr!` macro tries to construct a [`CStrStack`] with one of the given
4+
/// buffer sizes. If none of the stack sizes is large enough, it falls back to
5+
/// allocating a [`CString`] on the heap.
6+
///
7+
/// This makes it ergonomic to create FFI-safe strings with minimal overhead.
8+
/// Common short strings will use stack buffers, while longer strings
9+
/// automatically use heap allocation.
10+
///
11+
/// # Syntax
12+
///
13+
/// ```ignore
14+
/// cstr!([sizes...], "format string", args...)
15+
/// cstr!("format string", args...) // uses default sizes [32, 128]
16+
/// ```
17+
///
18+
/// - `sizes...`: A list of candidate stack buffer sizes.
19+
/// The macro tries each size in order until one succeeds.
20+
/// If all fail, a heap allocation is used.
21+
/// - `"format string", args...`: A format string and its arguments,
22+
/// just like in `format!`.
23+
///
24+
/// # Returns
25+
///
26+
/// A `Box<dyn CStrLike>`, which can be used to obtain:
27+
/// - a raw pointer (`*const c_char`) via [`CStrLike::as_ptr`]
28+
/// - a reference to the [`CStr`] via [`CStrLike::as_cstr`]
29+
///
30+
/// # Examples
31+
///
32+
/// ```
33+
/// use std::ffi::CStr;
34+
/// use stack_cstr::{cstr, CStrLike};
35+
///
36+
/// // Use default sizes [32, 128]
37+
/// let s = cstr!("Hello {}", 42);
38+
/// assert_eq!(s.as_cstr().to_str().unwrap(), "Hello 42");
39+
///
40+
/// // Explicit stack sizes
41+
/// let s = cstr!([16, 64], "Pi = {:.2}", 3.14159);
42+
/// assert_eq!(s.as_cstr().to_str().unwrap(), "Pi = 3.14");
43+
///
44+
/// unsafe {
45+
/// // Pass to FFI as *const c_char
46+
/// assert_eq!(CStr::from_ptr(s.as_ptr()).to_str().unwrap(), "Pi = 3.14");
47+
/// }
48+
/// ```
49+
///
50+
/// # Notes
51+
///
52+
/// - If the formatted string fits in one of the provided stack buffers,
53+
/// no heap allocation is performed.
54+
/// - If the string is too long for all stack buffers, it is allocated on the heap.
55+
/// - The returned type is `Box<dyn CStrLike>`, which is heap-allocated for
56+
/// type erasure even when the string is stack-based. This indirection is
57+
/// usually negligible.
58+
///
59+
/// # See also
60+
///
61+
/// - [`CStrStack`] for stack-only storage
62+
/// - [`CStrHeap`] for explicit heap allocation
163
#[macro_export]
264
macro_rules! cstr {
365
([$($size:expr),*], $($args:tt)*) => {{

0 commit comments

Comments
 (0)