cstr_literal/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(unsafe_op_in_unsafe_fn)]
3#![no_std]
4
5#[doc(hidden)]
6pub mod private {
7  use core::ffi::CStr;
8
9  pub extern crate const_format;
10
11  pub const fn new(s: &'static str) -> &'static CStr {
12    let mut bytes = s.as_bytes();
13    loop {
14      match bytes {
15        [0, _, ..] => panic!("C strings can't contain null bytes"),
16        [] => panic!("C strings must be null-terminated"),
17        [0] => break,
18        [_, remaining @ ..] => bytes = remaining,
19      }
20    }
21
22    unsafe { CStr::from_bytes_with_nul_unchecked(s.as_bytes()) }
23  }
24}
25
26/// Creates a [`&'static CStr`][`core::ffi::CStr`] from a const [`&'static str`][`str`].
27///
28/// The input string can be a literal or a const expression.
29///
30/// Under the hood, this macro uses [`const_format::concatcp`] to append
31/// the null terminator and therefore is subject to the same limitations.
32#[macro_export]
33macro_rules! cstr {
34  ($s:expr) => {{
35    const __CSTR_STR: &str = $crate::private::const_format::concatcp!($s, "\0");
36    const __CSTR: &::core::ffi::CStr = $crate::private::new(__CSTR_STR);
37    __CSTR
38  }};
39}
40
41#[cfg(test)]
42mod tests {
43  #[test]
44  fn valid() {
45    let t = trybuild::TestCases::new();
46    t.pass("tests/valid/*.rs");
47  }
48
49  #[test]
50  fn invalid() {
51    let t = trybuild::TestCases::new();
52    t.compile_fail("tests/invalid/*.rs");
53  }
54}