如何安全地从 `&mut [u32]` 中获取不可变字节切片?
How to safely get an immutable byte slice from a `&mut [u32]`?
在我的项目的一个相当低级别的部分,一个函数接收原始数据的可变切片(在本例中为 &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
)。
此代码有效,但关键问题是:这安全吗? 特别是,在某些时候,data
和 bytes
都存在,是同一数据的一个可变切片和一个不可变切片。这听起来很糟糕,对吧?
另一方面,我可以这样做:
let bytes = &data[..];
那样的话,那两片我也有。不同之处在于 data
现在是借用的。
我的代码安全还是存在 UB?为什么?如果不安全,如何安全的做我想做的事?
一般来说,创建违反 Rust 安全规则的切片,即使是短暂的,也是不安全的。如果您欺骗借用检查器并使独立切片同时借用与 &
和 &mut
相同的数据,这将使 Rust 在 LLVM 中指定不正确的别名信息,这可能导致实际编译错误的代码. Miri doesn't flag this case, because you're not using data
afterwards, but the exact details of what is unsafe are still being worked out.
为了安全起见,您应该向借阅检查员说明共享情况:
let shared_data = &data[..];
data
将临时重新借用 shared/read-only,使用期限为 shared_data
。在这种情况下,它不应该造成任何限制。 data
将在退出此范围后保持可变。
那么您将有 &[u32]
,但您需要 &[u8]
。幸运的是,这个 转换是安全的,因为两者是共享的,并且 u8
比 u32
有更少的对齐要求(如果是另一种方式,你' d 必须使用 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)
};
在我的项目的一个相当低级别的部分,一个函数接收原始数据的可变切片(在本例中为 &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
)。
此代码有效,但关键问题是:这安全吗? 特别是,在某些时候,data
和 bytes
都存在,是同一数据的一个可变切片和一个不可变切片。这听起来很糟糕,对吧?
另一方面,我可以这样做:
let bytes = &data[..];
那样的话,那两片我也有。不同之处在于 data
现在是借用的。
我的代码安全还是存在 UB?为什么?如果不安全,如何安全的做我想做的事?
一般来说,创建违反 Rust 安全规则的切片,即使是短暂的,也是不安全的。如果您欺骗借用检查器并使独立切片同时借用与 &
和 &mut
相同的数据,这将使 Rust 在 LLVM 中指定不正确的别名信息,这可能导致实际编译错误的代码. Miri doesn't flag this case, because you're not using data
afterwards, but the exact details of what is unsafe are still being worked out.
为了安全起见,您应该向借阅检查员说明共享情况:
let shared_data = &data[..];
data
将临时重新借用 shared/read-only,使用期限为 shared_data
。在这种情况下,它不应该造成任何限制。 data
将在退出此范围后保持可变。
那么您将有 &[u32]
,但您需要 &[u8]
。幸运的是,这个 转换是安全的,因为两者是共享的,并且 u8
比 u32
有更少的对齐要求(如果是另一种方式,你' d 必须使用 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)
};