如何安全地从`&mut [u32]`获取不可变的字节片?

时间:2019-05-02 10:29:00

标签: rust unsafe

在我的项目的相当低级的部分中,函数接收可变的原始数据切片(在这种情况下为&mut [u32])。此数据应以小端序写入作者。

现在,仅此一项就不成问题,但是所有这些必须快速。我评估了我的应用程序,并将其确定为关键路径之一。特别是,如果不需要更改字节序(因为我们已经在一个小的字节序系统上),那么就不会有任何开销。

这是我的代码(Playground):

use std::{io, mem, slice};

fn write_data(mut w: impl io::Write, data: &mut [u32]) -> Result<(), io::Error> {
    adjust_endianness(data);

    // Is this safe?
    let bytes = unsafe {
        let len = data.len() * mem::size_of::<u32>();
        let ptr = data.as_ptr() as *const u8;
        slice::from_raw_parts(ptr, len)
    };

    w.write_all(bytes)
}

fn adjust_endianness(_: &mut [u32]) {
    // implementation omitted
}

adjust_endianness更改了字节序(这很好,因为错误的字节序u32是垃圾,但仍然是有效的u32)。

此代码有效,但关键问题是:这样安全吗?特别是,在某个时候,databytes都存在,它们是一个可变的且一个不变的切片到相同的数据。听起来很不好,对吧?

另一方面,我可以这样做:

let bytes = &data[..];

那样,我也有那两个切片。区别只是data现在已借用。

我的代码安全吗?它可以显示UB吗?为什么?如果不安全,该如何安全地做我想做的事?

1 个答案:

答案 0 :(得分:0)

通常,创建违反Rust安全规则的切片(即使是短暂的)也是不安全的。如果您欺骗借阅检查器并制作独立的切片,同时借用与&&mut相同的数据,这将使Rust在LLVM中指定不正确的别名信息,这可能会导致编译错误的代码。 Miri不会标记这种情况,因为此后您将不使用data,而是有关不安全的are still being worked out的确切详细信息。

为了安全起见,您应该向借阅检查员解释共享情况:

let shared_data = &data[..];
在使用shared_data期间,

bytes将作为共享/只读临时重新借入。在这种情况下,它不会造成任何限制。 data在退出此范围后将保持可变。

然后您将拥有&[u32],但您需要&[u8]。幸运的是,转换是安全的,因为两者都是共享的,并且u8的对齐要求比u32少(如果相反,则必须使用align_to!)。

let shared_data = &data[..];
let bytes = unsafe {
    let len = shared_data.len() * mem::size_of::<u32>();
    let ptr = data.as_ptr() as *const u8;
    slice::from_raw_parts(ptr, len)
};