对于任何大小为非4的调用,您都可以使编译失败,但这不会使转换工作.您需要使用transmute_copy()
:
fn foo<T>(t: T) {
struct AssertSize4<T>(T);
impl<T> AssertSize4<T> {
const ASSERT: () = if std::mem::size_of::<T>() != 4 {
panic!("size is not 4 bytes");
};
}
let _ = AssertSize4::<T>::ASSERT;
let t = std::mem::ManuallyDrop::new(t);
unsafe {
std::mem::transmute_copy::<T, [u8; 4]>(&*t);
};
}
请注意,这将生成post-monormphization错误,这意味着它将成功cargo check
,但失败cargo build
.
然而,您的代码仍然是不正确的:某个东西的大小是4个字节doesn't mean,它可以自由转换为4个字节.以下面的 struct 为例:
#[repr(C)]
struct Evil {
_a: u8,
_b: u16,
}
此 struct 的大小为4个字节,但将其传递给foo()
将调用未定义的行为.这是因为它有填充字节,相当于未初始化的字节(单元化内存的另一个例子是未初始化的MaybeUninit
).Transmuting uninitialized bytes to any type that does not allow uninitialized bytes, and that includes integers such as u8
, is immediate Undefined Behavior.
一种可能性是将数据转化为[MaybeUninit<u8>; 4]
.但是,您可能永远不会认为这些字节是通过对它们执行诸如assume_init()
之类的操作来初始化的.
另一个操作是确保字节被初始化.那么,如何只接受只具有初始化字节的类型呢?
你不能这么做.您唯一的机会就是创建一个unsafe trait OnlyInitialized
,然后不安全地将其应用于类型.您可能还想为它提供一个派生宏(但判断是微妙的!不要假设你可以把它们都想起来).此时,只需使用bytemuck
即可.