在 Rust 中将结构体转换为数组

Bab*_*bur 5 undefined-behavior rust

假设我们有一个结构,其所有字段都具有相同大小的类型:

struct Homogeneous {
    a: u64,
    b: u64,
    c: u64,
    d: u64
}
Run Code Online (Sandbox Code Playgroud)

我们有一种“安全”的方法来从字节数组构造它:

impl From<[u8; 32]> for Homogeneous {
    fn from(slice: [u8; 32]) -> Self {
       // helper macro to convert slice of u8s into u64
       macro_rules! to_u64 {
            ($slice: expr, $at: expr) => {{
                let ss = &$slice[$at..$at + 8];
                let mut buf = [0u8; 8];
                buf.copy_from_slice(&ss);
                u64::from_ne_bytes(buf)
            }};
        }
        
        Self {
            a: to_u64!(bytes, 0),
            b: to_u64!(bytes, 8),
            c: to_u64!(bytes, 16),
            d: to_u64!(bytes, 24),
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这一切都很好并且有效。问题是不安全的解决方案(使用transmute)是否更有效(安全?),以及反向转换是否不会由于优化编译器重新排序结构字段而导致 UB?

   impl From<[u8; 32]> for Homogeneous {
       fn from(slice: [u8; 32]) -> Self {
           unsafe { std::mem::transmute(slice) };
       }
   }
   
   impl From<Homogeneous> for [u8; 32] {
       fn from(h: Homogeneous) -> Self {
           unsafe { std::mem::transmute(h) }
       }
   } 
Run Code Online (Sandbox Code Playgroud)

这些转换在我的 x86 处理器上使用 rust 1.57 编译器工作,我想知道它们是否总是工作,不管架构/编译器如何。

Net*_*ave 7

来自rustlang 参考

默认情况下,结构体的内存布局是未定义的,以允许编译器优化(例如字段重新排序),但可以使用repr 属性进行修复。在任何一种情况下,字段都可以在相应的结构表达式中以任何顺序给出;生成的结构体值将始终具有相同的内存布局。

这意味着不能保证属性会按照您的意愿排列。因此,您必须在实施中确保它始终有效。

例如使用#[repr(c)]

#[repr(c)]
struct Homogeneous {
    a: u64,
    b: u64,
    c: u64,
    d: u64
}
Run Code Online (Sandbox Code Playgroud)


Cae*_*sar 7

Netwave已经回答了有关安全的部分问题。

对于“更高效”的部分,godbolt 来救援

你的代码产生

<example::Homogeneous as core::convert::From<[u8; 32]>>::from:
        mov     rax, rdi
        movups  xmm0, xmmword ptr [rsi]
        movups  xmm1, xmmword ptr [rsi + 16]
        movups  xmmword ptr [rdi], xmm0
        movups  xmmword ptr [rdi + 16], xmm1
        ret
Run Code Online (Sandbox Code Playgroud)

#[repr(C)]
pub struct HomogeneousC { a: u64, b: u64, c: u64, d: u64 }

impl From<[u8; 32]> for HomogeneousC {
    fn from(bytes: [u8; 32]) -> Self {
       unsafe { std::mem::transmute(bytes) }
    }
}
Run Code Online (Sandbox Code Playgroud)

产量

<example::HomogeneousC as core::convert::From<[u8; 32]>>::from:
        mov     rax, rdi
        movups  xmm0, xmmword ptr [rsi]
        movups  xmm1, xmmword ptr [rsi + 16]
        movups  xmmword ptr [rdi + 16], xmm1
        movups  xmmword ptr [rdi], xmm0
        ret
Run Code Online (Sandbox Code Playgroud)

因此,LLVM 很好地优化了安全版本的所有缺陷,它们可能具有大致相同的性能。