有什么方法可以创建 const &'static CStr 吗?

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

我在标准库中没有找到任何关于如何制作 const &'static CStr 的内容。我试图制作自己的宏以将 &'static str 文字转换为 &'static CStr:

macro_rules! cstr {
    ($e: expr) => {{
        const buffer: &str = concat!($e, "[=10=]");
        unsafe {std::ffi::CStr::from_bytes_with_nul_unchecked(buffer.as_bytes())}
    }}                                                                           
}     

它有几个问题:

  1. 如果 expr 包含空字节,它会调用未定义的行为
  2. str::as_bytes 不是 const,所以 &CStr 不是 const

A CStrborrowed type and, as such, isn't made "on its own". Below the hood, it isn't much more than a reference to a CString,可以从以下任一创建:

  • 借用一个CString(显而易见)。原始(源)CString不得删除,CStr的生命周期仅在源存在时有效
  • 来自一段字节,通过 CStr::from_bytes_with_nulCStr 仅在原始切片期间有效(原始切片本身仅在源数据分配到 某处 时有效)

通过 CString 创建 CStr 很简单:

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

转换现有切片也很简单:

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

请注意,这些的生命周期显然再次取决于您用来创建 &CStr 的任何生命周期 - 如其声明中的生命周期参数所示


为后代保留'static 不是硬性要求

要创建一个 const &'static CStr,你会很困难,你需要一个用于特定宏的外部 crate (lazy_static),但它是可行的,就像这样:

#[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[=12=]".as_bytes())
    };
}

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

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

lazy_static的要点是在定义静态引用时允许函数调用;我们可以利用它来即时构建我们的 CStr,并且由于它是一个静态引用,因此借用它最多有效 'static。任务完成。

有一个箱子,byte_strings。总结一下 crate,基本思想是使用一个包含 &'static [u8](或 &'static str)成员和 &'static CStr 成员的联合:

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

由于构建联合是 const 并且访问 const 联合的字段也是 const,因此读取 dst 实际上是 const mem::transmute.由于 CStr 目前只是 [c_char] 的包装器,因此 &[u8] 可以安全地转换为 &CStr,但是,在将来,CStr 的表示s 可能会改变。您可以通过对零大小数组的长度使用一些技巧来检查 &CStr 是否与 &[u8] 大小相同:

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

如果它们的大小不同,Rust 的类型检查器会报错。 综合起来,你可以创建一个宏来制作 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, "[=12=]") }).dst
        };

        RES
    }}                                                                           
}

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

不幸的是,这个宏仍然不安全,因为它不检查 &str 切片中的空字节,这只能通过过程宏来完成。 byte_strings crate contains such a macro,以及用于连接字节字符串文字和其他方便宏的宏。

从 Rust 1.46.0(撰写本文时的当前 beta 工具链)开始,这是可能的,现在 std::mem::transmute 作为 const fn 是稳定的。您还可以使用 const fns 检查字符串的内容是否有效(即没有空字节),因为您也可以使用基本的条件表达式和循环。通过 panic! 恐慌在常量上下文中尚不可能,但您可以使用隐式恐慌代码(例如 [][0])在编译时引发错误。总而言之,这是一个功能齐全的示例,它仅使用 const fns 和声明性宏来允许在常量上下文中创建 &'static CStrs,包括检查非法空字节的内容。

#[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'[=10=]' {
            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, "[=10=]")) }
    }};
}

const VALID: &std::ffi::CStr = cstr!("hello world");
// const INVALID: &std::ffi::CStr = cstr!("hello[=10=]world");

fn main() {
    println!("Output: {:?}", VALID);
}

请注意,这确实依赖于 CStr 的实现细节(特别是布局与 [u8] 兼容),因此不应在生产代码中使用。