I haven't found anything in the standard library about how to make a const &'static CStr
. I've tried to make my own macro to convert a &'static str
literal to a &'static CStr
:
macro_rules! cstr {
($e: expr) => {{
const buffer: &str = concat!($e, "\0");
unsafe {std::ffi::CStr::from_bytes_with_nul_unchecked(buffer.as_bytes())}
}}
}
It has a couple problems:
expr
contains a null byte, it invokes undefined behaviorstr::as_bytes
is not const
, so the &CStr
is not constAs of Rust 1.46.0 (current beta toolchain at time of writing) this is possible, now that std::mem::transmute
is stable as a const fn
. You can also use const fn
s to check that the contents of the string are valid (i.e. no null bytes), since you can use basic conditional expressions and loops as well. Panicking via panic!
isn't yet possible in constant contexts, but you can use implicitly panicking code (e.g. [][0]
) to raise an error at compile time. All told, here's a fully functional example that uses nothing but const fn
s and declarative macros to allow creating &'static CStr
s in constant contexts, including checking the contents for illegal null bytes.
#[allow(unconditional_panic)]
const fn illegal_null_in_string() {
[][0]
}
#[doc(hidden)]
pub const fn validate_cstr_contents(bytes: &[u8]) {
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'\0' {
illegal_null_in_string();
}
i += 1;
}
}
macro_rules! cstr {
( $s:literal ) => {{
$crate::validate_cstr_contents($s.as_bytes());
unsafe { std::mem::transmute::<_, &std::ffi::CStr>(concat!($s, "\0")) }
}};
}
const VALID: &std::ffi::CStr = cstr!("hello world");
// const INVALID: &std::ffi::CStr = cstr!("hello\0world");
fn main() {
println!("Output: {:?}", VALID);
}
Note that this does rely on implementation details of CStr
(specifically that the layout is compatible with [u8]
), so this shouldn't be used in production code.