constant_cstr/
lib.rs

1//! `constant-cstr` exists to enable the safe creation of [`CStr`](std::ffi::CStr) instances at
2//! compile time, enabling safer and more efficient FFI.
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{parse_macro_input, LitByteStr, LitStr};
6
7/// `cstr` checks its input for null bytes and, if none are found, generates an [`&'static
8/// CStr`](std::ffi::CStr). It must be provided a string literal.
9/// # Examples
10/// ```
11/// use std::ffi::CStr;
12/// use constant_cstr::cstr;
13///
14/// const DEV_PTMX: &'static CStr = cstr!("/dev/ptmx");
15/// ```
16/// Passing an input string with a null byte will cause a compile error with a message indicating
17/// the position of the null byte:
18/// ```compile_fail
19/// use std::ffi::CStr;
20/// use constant_cstr::cstr;
21///
22/// const HELLO: &'static CStr = cstr!("Hell\0, world");
23/// ```
24/// ```text,no_run
25/// error: proc macro panicked
26///   --> src/example.rs:4:34
27///    |
28///  4 |     const HELLO: &'static CStr = cstr!("Hell\0, world");
29///    |                                  ^^^^^^^^^^^^^^^^^^^^^^
30///    |
31///    = help: message: "Hell\0, world" contains a null byte at position 4
32/// ```
33#[proc_macro]
34pub fn cstr(input: TokenStream) -> TokenStream {
35    let input_clone = input.clone();
36    let input_str = parse_macro_input!(input as LitStr);
37    let mut input_bytes = input_str.value().into_bytes();
38    if let Some(null_idx) = input_bytes.iter().position(|n| *n == 0) {
39        panic!(
40            "{} contains a null byte at position {}",
41            input_clone, null_idx
42        );
43    }
44    input_bytes.push(0);
45    let output_bytes = LitByteStr::new(&input_bytes, input_str.span());
46    quote! { unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(#output_bytes) } }.into()
47}