Skip to content

Commit 8e26b75

Browse files
committed
feat: Created the stack-cstr library for efficient C-style string generation.
- Implemented stack-based C string generation with support for configurable stack buffer sizes. - Provided automatic fallback to heap allocation to handle strings that exceed the stack buffer size. - Supported `format_args!`-style formatting. - Provided a simple `cstr!()` macro interface for ease of use. - Implemented the `CStrLike` trait to simplify FFI usage. - Added unit tests to ensure correct functionality.
1 parent de45c3b commit 8e26b75

File tree

9 files changed

+192
-0
lines changed

9 files changed

+192
-0
lines changed

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "stack-cstr"
3+
version = "0.1.0"
4+
edition = "2024"
5+
authors = ["Junkang Yuan <[email protected]>"]
6+
description = "High-performance stack-to-heap C string creation for Rust with FFI support"
7+
repository = "https://github.com/fxdmhtt/stack-cstr"
8+
license = "GPL-2.0"
9+
readme = "README.md"
10+
keywords = ["CStr", "CString", "FFI"]
11+
categories = ["ffi", "formatting", "string"]
12+
13+
[dependencies]
14+
arrayvec = "0.7.6"

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# stack_cstr
2+
3+
`stack_cstr` is a high-performance Rust library for creating C-style strings (CStr/CString) efficiently.
4+
It tries to write the formatted string into a stack buffer first, and if the string is too long, it falls back to heap allocation.
5+
The final result is a safe C string that can be passed to FFI functions.
6+
7+
## Features
8+
9+
- Stack buffer attempt with configurable sizes
10+
- Automatic heap fallback for long strings
11+
- Supports `format_args!` style formatting
12+
- Returns `Box<dyn CStrLike>` for easy FFI usage
13+
- Simple macro interface: `cstr!()`
14+
- Extensible stack sizes

src/cstr_heap.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use std::ffi::{CStr, CString, c_char};
2+
3+
use crate::CStrLike;
4+
5+
pub struct CStrHeap {
6+
cstr: CString,
7+
}
8+
9+
impl CStrHeap {
10+
pub fn new(s: CString) -> Self {
11+
Self { cstr: s }
12+
}
13+
14+
pub fn as_ptr(&self) -> *const c_char {
15+
self.cstr.as_ptr()
16+
}
17+
18+
pub fn as_cstr(&self) -> &CStr {
19+
self.cstr.as_c_str()
20+
}
21+
}
22+
23+
impl CStrLike for CStrHeap {
24+
fn as_ptr(&self) -> *const c_char {
25+
self.as_ptr()
26+
}
27+
28+
fn as_cstr(&self) -> &CStr {
29+
self.as_cstr()
30+
}
31+
}

src/cstr_like.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use std::ffi::{CStr, c_char};
2+
3+
pub trait CStrLike {
4+
fn as_ptr(&self) -> *const c_char;
5+
fn as_cstr(&self) -> &CStr;
6+
}

src/cstr_stack.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use std::ffi::{CStr, c_char};
2+
3+
use arrayvec::ArrayString;
4+
5+
use crate::CStrLike;
6+
7+
pub struct CStrStack<const N: usize> {
8+
buf: [u8; N],
9+
len: usize,
10+
}
11+
12+
impl<const N: usize> CStrStack<N> {
13+
pub fn new(fmt: std::fmt::Arguments) -> Result<CStrStack<N>, &'static str> {
14+
let mut buf: ArrayString<N> = ArrayString::new();
15+
std::fmt::write(&mut buf, fmt).map_err(|_| "format failed")?;
16+
17+
let bytes = buf.as_bytes();
18+
if bytes.len() + 1 > N {
19+
return Err("stack buffer overflow");
20+
}
21+
22+
let mut c_buf: [u8; N] = [0; N];
23+
c_buf[..bytes.len()].copy_from_slice(bytes);
24+
c_buf[bytes.len()] = 0;
25+
26+
Ok(CStrStack {
27+
buf: c_buf,
28+
len: bytes.len(),
29+
})
30+
}
31+
32+
pub fn as_ptr(&self) -> *const c_char {
33+
self.buf.as_ptr() as *const c_char
34+
}
35+
36+
pub fn as_cstr(&self) -> &CStr {
37+
unsafe { CStr::from_bytes_with_nul_unchecked(&self.buf[..self.len + 1]) }
38+
}
39+
}
40+
41+
impl<const N: usize> CStrLike for CStrStack<N> {
42+
fn as_ptr(&self) -> *const c_char {
43+
self.as_ptr()
44+
}
45+
46+
fn as_cstr(&self) -> &CStr {
47+
self.as_cstr()
48+
}
49+
}

src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub mod cstr_heap;
2+
pub mod cstr_like;
3+
pub mod cstr_stack;
4+
pub mod macros;
5+
6+
pub use cstr_heap::CStrHeap;
7+
pub use cstr_like::CStrLike;
8+
pub use cstr_stack::CStrStack;

src/macros.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#[macro_export]
2+
macro_rules! cstr {
3+
([$($size:expr),*], $($args:tt)*) => {{
4+
let args = format_args!($($args)*);
5+
6+
let result: Box<dyn $crate::CStrLike> = if false { unreachable!() }
7+
$(
8+
else if let Ok(s) = $crate::CStrStack::<$size>::new(args) {
9+
Box::new(s)
10+
}
11+
)*
12+
else {
13+
Box::new($crate::CStrHeap::new(std::ffi::CString::new(format!($($args)*)).unwrap()))
14+
};
15+
16+
result
17+
}};
18+
($($args:tt)*) => {
19+
cstr!([32, 128], $($args)*)
20+
};
21+
}

tests/cstr_tests.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use std::ffi::CStr;
2+
3+
use stack_cstr::cstr;
4+
5+
#[test]
6+
fn test_cstr_macro() {
7+
let c1 = cstr!("hi");
8+
9+
let c2 = "x".repeat(100);
10+
let c2 = cstr!("{}", c2);
11+
12+
let c3 = "x".repeat(300);
13+
let c3 = cstr!("{}", c3);
14+
15+
let c4 = cstr!([16, 64, 128], "explicit sizes");
16+
17+
assert_eq!(
18+
unsafe { CStr::from_ptr(c1.as_ptr()).to_str().unwrap() },
19+
"hi"
20+
);
21+
assert_eq!(
22+
unsafe { CStr::from_ptr(c2.as_ptr()).to_str().unwrap() },
23+
&"x".repeat(100)
24+
);
25+
assert_eq!(
26+
unsafe { CStr::from_ptr(c3.as_ptr()).to_str().unwrap() },
27+
&"x".repeat(300)
28+
);
29+
assert_eq!(
30+
unsafe { CStr::from_ptr(c4.as_ptr()).to_str().unwrap() },
31+
"explicit sizes"
32+
);
33+
}

0 commit comments

Comments
 (0)