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

5

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

struct Homogeneous {
    a: u64,
    b: u64,
    c: u64,
    d: u64
}

我们有一种“安全”的方法,可以从字节数组构建它:

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),
        }
    }
}

这一切都很好,而且它也能正常运作。问题在于不安全的解决方案(使用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) }
       }
   } 

使用 Rust 1.57 编译器,这些转换在我的 x86 处理器上运行良好。我想知道它们是否会始终如此,无论架构/编译器如何。


重新阅读问题后,你的宏其实可以简化为一行闭包:let to_bytes = |i| u64::from_ne_bytes(bytes[i..][..8].try_into().unwrap()); Self { a: to_bytes(0), b: to_bytes(8), … }。生成的字节码相同。 - Caesar
2个回答


7

根据 rustlang 参考文档:

默认情况下,结构体的内存布局未定义,以允许编译器进行优化(如字段重新排序),但是可以通过repr 属性固定。在任何情况下,相应结构体表达式中的字段可以按任意顺序给出; 结果结构体值的内存布局始终相同。

这意味着不能保证属性将按您希望的方式排列。因此,您必须在实现中确保它,以便始终有效。

例如使用#[repr(c)]

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

想到了,谢谢你的确认和推荐。 - Babur Makhmudov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接