常量和静态CStr

时间:2019-10-11 23:23:38

标签: rust ffi

我没有在标准库中找到有关如何制作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包含空字节,则会调用未定义的行为
  2. str::as_bytes不是const,因此&CStr不是const

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

3 个答案:

答案 0 :(得分:3)

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

#[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]兼容),因此不应在生产代码中使用。

答案 1 :(得分:2)

CStrborrowed type,因此不是“单独”制作的。在引擎盖下,它仅是对CString的引用,并且可以通过以下任一方式创建:

  • 借用CString(显而易见)。原始(来源)CString不得删除,CStr的生存期仅在来源存在的前提下有效
  • 从一片字节中通过CStr::from_bytes_with_nulCStr的有效期仅与原始切片的时间相同(其本身仅与在 somewhere 处分配的源数据一样有效)

通过CStr创建CString很简单:

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,您会很费力,并且需要一个用于特定宏(lazy_static)的外部包装箱,但这是可行的,例如: / p>

#[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。任务完成了。

答案 2 :(得分:0)

对此有一个箱子,byte_strings。总结一下箱子,基本思想是使用具有private fun animateMainView(drawerView: View, slideOffset: Float) { val scaleFactor = 6f val slideX = drawerView.width * slideOffset fragment.view?.translationX = slideX fragment.view?.scaleX = 1 - (slideOffset / scaleFactor) fragment.view?.scaleY = 1 - (slideOffset / scaleFactor) } (或&'static [u8])成员和&'static str成员的联合:

&'static CStr

由于构造联合是union transmute { src: &'static [u8], dst: &'static ::std::ffi::CStr, } ,并且访问const联合字段也是const,因此读const实际上是dst mem :: transmute。由于const目前仅是CStr的包装,因此可以安全地将[c_char]截断为&[u8],但是将来,&CStr的表示形式可能会改变。您可以通过使用长度为零大小的数组的小技巧,来检查CStr&CStr的大小是否相同:

&[u8]

如果它们的大小不相同,Rust的类型检查器将抱怨。 将它们放在一起,您可以创建一个宏来制作一个const transmute_is_sound_guard: [(); std::mem::size_of::<&'static [u8]>()] = [(); std::mem::size_of::<&'static ::std::ffi::CStr>()];

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) } 片中的空字节,这只能通过程序宏来完成。 byte_strings板条箱contains such a macro,以及用于连接字节字符串文字和其他便利宏的宏。