Vaelus
Vaelus

Reputation: 1065

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

Upvotes: 6

Views: 3728

Answers (4)

Chayim Friedman
Chayim Friedman

Reputation: 71380

Since Rust 1.77.0, on edition 2021 and forwards, you can create &CStr literals with the c prefix:

const FOO: &core::ffi::CStr = c"Hello, world!";

This allows using escapes (even non-UTF-8), checks for interior NUL bytes (and rejects them), and properly adds a trailing NUL.

On older versions, you can use the cstr crate:

const FOO: &core::ffi::CStr = cstr::cstr!(b"Hello, world!");

Upvotes: 9

apetranzilla
apetranzilla

Reputation: 5959

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.

Upvotes: 6

Vaelus
Vaelus

Reputation: 1065

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.

Upvotes: 4

S&#233;bastien Renauld
S&#233;bastien Renauld

Reputation: 19672

A CStr is a borrowed type and, as such, isn't made "on its own". Below the hood, it isn't much more than a reference to a CString, and can be created from either:

  • Borrowing a CString (obvious). The original (source) CString must not be dropped and the lifetime of CStr is only valid for as long as the source exists
  • From a slice of bytes, via CStr::from_bytes_with_nul. The CStr is only valid for as long as the original slice (which itself is only valid for as long as the source data allocated somewhere)

Creating a CStr through a CString is straightforward:

let cstring:CString = CString::new("foobar".as_bytes()).unwrap();
let cstr:&CStr = cstring.as_c_str();
println!("{:?}", cstr);

Converting an existing slice is also straightforward:

let cstr2:&CStr = CStr::from_bytes_with_nul("foobar\0".as_bytes()).unwrap();
println!("{:?}", cstr2);

Do note that the lifetime of these will evidently, again, depend on the lifetime of whatever you used to create the &CStr - as indicated by the lifetime parameter on its declaration


Kept for posterity: 'static was not a hard requirement

To create a const &'static CStr, you're going to struggle, and you're going to need an external crate for a specific macro (lazy_static), but it is doable, like so:

#[macro_use] extern crate lazy_static;
use std::ffi::CStr;

lazy_static! {
    static ref FOO:&'static CStr = unsafe {
        CStr::from_bytes_with_nul_unchecked("foobar\0".as_bytes())
    };
}

fn test(input: &'static CStr) {
    println!("{:?}", FOO.to_str());
}

fn main() {
    test(&FOO);
}

The point of lazy_static is to allow function calls when defining static references; we can leverage this to construct our CStr on-the-fly, and since it is a static reference, borrowing it is valid for up to and including 'static. Mission accomplished.

Upvotes: 2

Related Questions