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.
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 CStr
s 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With