前言

最近在rust 调用c的时候遇到一个坑,需要从c里面把char *转成rust的slice类型。 最初使用CStr::from_ptr函数,最后发现数据不对。

一段不正确的代码

下面的代码,在返回二进制数据,有时候不全

1
2
3
4
let slice = CStr::from_ptr(grammar_data as *const c_char)
    .to_bytes()
    .to_vec();

阅读from_ptr实现

看了from_ptr实现,焕然大悟,构造的len是通过sys::strlen函数取得。 c里面的char *是个裸指针,字符串的长度是通过,找到末尾的0计算得到。 如果数据里面有0,那返回的 rust slice就是错误的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    #[stable(feature = "rust1", since = "1.0.0")]
    pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> &'a CStr {
        // SAFETY: The caller has provided a pointer that points to a valid C
        // string with a NUL terminator of size less than `isize::MAX`, whose
        // content remain valid and doesn't change for the lifetime of the
        // returned `CStr`.
        //
        // Thus computing the length is fine (a NUL byte exists), the call to
        // from_raw_parts is safe because we know the length is at most `isize::MAX`, meaning
        // the call to `from_bytes_with_nul_unchecked` is correct.
        //
        // The cast from c_char to u8 is ok because a c_char is always one byte.
        unsafe {
            let len = sys::strlen(ptr);
            let ptr = ptr as *const u8;
            CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(ptr, len as usize + 1))
        }
    }

正解代码

好了,找到原因,修复就简单。std::slice::form_raw_parts可以传递裸指针长度构造一个slice。 这里有个小插曲,grammar_data是*mut ::std::os::raw::c_void;类型,直接通过fs::write(file_name, slice)写入slice 是不行的。所以这里把grammar_data 强转成 *const u8类型。

1
2
3
4
unsafe {
    let slice = std::slice::from_raw_parts(grammar_data as *const u8, size as usize);
}

github代码示例

https://github.com/guonaihong/rust-example/tree/main/raw-ptr-to-slice