rust 有没有办法创建一个const &'static CStr?

ovfsdjhp  于 12个月前  发布在  其他
关注(0)|答案(4)|浏览(147)

我在标准库中没有找到任何关于如何生成const &'static CStr的内容。我尝试自己编写宏来将&'static str转换为&'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())}
    }}                                                                           
}

字符串
它有几个问题:
1.如果expr包含空字节,则调用未定义行为

  1. str::as_bytes不是const,所以&CStr不是const
cqoc49vn

cqoc49vn1#

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

#[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);
}

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

bqujaahr

bqujaahr2#

有一个crate,byte_strings。总结一下crate,基本思想是使用一个union,其中有一个&'static [u8](或&'static str)成员和一个&'static CStr成员:

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

字符串
由于构造union是const,访问const union的字段也是const,因此阅读dst实际上是const的转换。由于CStr目前只是[c_char]的 Package 器,因此&[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, "\0") }).dst
        };

        RES
    }}                                                                           
}

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


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

dtcbnfnu

dtcbnfnu3#

一个CStr是一个borrowed type,因此,它不是“独立”生成的。在底层,它只不过是一个CString的引用,可以从以下两种方式中创建:

  • 借用CString(显而易见)。原始(源)CString不能被删除,CStr的生存期仅在源存在时有效
  • 通过CStr::from_bytes_with_nul从一个字节切片中提取。CStr仅在原始切片中有效(原始切片本身仅在源数据分配 * 某处 * 时有效)

通过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\0".as_bytes()).unwrap();
println!("{:?}", cstr2);


请注意,这些文件的生存期显然取决于您用来创建&CStr的任何文件的生存期,如其声明中的lifetime参数所示

留给后人'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\0".as_bytes())
    };
}

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

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


lazy_static的要点是在定义静态引用时允许函数调用;我们可以利用这一点来动态地构建CStr,由于它是一个静态引用,因此借用它对于'static(包括'static)都是有效的。使命完成。

ig9co6j1

ig9co6j14#

在2021版及以后版本的夜间(计划为1.76.0,将于2024年2月8日发布),您可以使用c前缀创建&CStr文字:

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

字符串
这允许使用转义(甚至非UTF-8),检查内部NUL字节(并拒绝它们),并正确添加尾随NUL。
在旧版本中,您可以使用cstr crate:

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

相关问题