Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

ffi

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
like image 333
Vaelus Avatar asked Jan 01 '23 14:01

Vaelus


2 Answers

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.

like image 59
apetranzilla Avatar answered Jan 13 '23 20:01

apetranzilla


There is a crate for this, byte_strings. To summarize the crate, the basic idea is to use a union with a &'static [u8] (or &'static str) member and a &'static CStr member:

union transmute {
    src: &'static [u8],
    dst: &'static ::std::ffi::CStr,
}

Since constructing unions is const and accessing a const union's field is also const, reading dst is effectively a const mem::transmute. Since CStr is currently just a wrapper for a [c_char], a &[u8] can be safely trunsmuted to &CStr, however, in the future, the representation of CStrs will likely change. You can do a sanity check that &CStr is the same size as &[u8] by using a little hack with the lengths of zero-size arrays:

const transmute_is_sound_guard: [(); std::mem::size_of::<&'static [u8]>()]
    = [(); std::mem::size_of::<&'static ::std::ffi::CStr>()];

If they don't have the same size, Rust's type checker will complain. Bringing it all together, you can create a macro to make a const &'static CStr:

use std::ffi::CStr;
use std::mem::size_of;

macro_rules! unsafe_cstr {
    ($e: expr) => {{
        union Transmute {
            src: &'static str,
            dst: &'static CStr,
        }

        const _TRANSMUTE_CHECK: [(); size_of::<&'static str>()]
            = [(); size_of::<&'static CStr>()];

        const RES: &'static CStr = unsafe {
            (Transmute { src: concat!($e, "\0") }).dst
        };

        RES
    }}                                                                           
}

fn main() {
    const C: &'static CStr = unsafe_cstr!("Hello, World!");
    println!("{:?}", C)
}

Unfortunately, this macro still isn't safe, because it doesn't check for null bytes within the &str slice, which can only be done with a procedural macro. The byte_strings crate contains such a macro, as well as macros for concatenating byte string literals and other convenience macros.

like image 25
Vaelus Avatar answered Jan 13 '23 20:01

Vaelus