rustffi

Is there any way to create a const &'static CStr?


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:

  1. If expr contains a null byte, it invokes undefined behavior
  2. str::as_bytes is not const, so the &CStr is not const

Solution

  • As 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 fns 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 fns and declarative macros to allow creating &'static CStrs 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.