替代表示

Rust 允许你指定与默认值不同的数据布局策略。 还有 不安全代码指南(请注意,它不是规范性的)。

repr(C)

这是最重要的 repr。它的意图相当简单:按照 C 的方式进行。字段的顺序、大小和对齐方式与你在 C 或 C++ 中期望的完全相同。任何你期望通过 FFI 边界传递的类型都应该具有 repr(C),因为 C 是编程世界的通用语言。这也是稳妥地使用更复杂的数据布局技巧(例如将值重新解释为不同类型)所必需的。

我们强烈建议使用 rust-bindgen 和/或 cbindgen 来为你管理 FFI 边界。Rust 团队与这些项目紧密合作,以确保它们能够可靠地工作,并且与当前和未来的类型布局和 repr 保证兼容。

必须记住 repr(C) 与 Rust 更为奇特的数据布局特性的交互。由于它作为“用于 FFI”和“用于布局控制”的双重目的,repr(C) 可以应用于如果通过 FFI 边界传递将会毫无意义或产生问题的类型。

  • ZST 仍然是零大小的,即使这在 C 中不是标准行为,并且明确与 C++ 中空类型的行为相反,C++ 中空类型仍然应该消耗一个字节的空间。

  • DST 指针(宽指针)和元组在 C 中不是概念,因此永远不是 FFI 安全的。

  • 带有字段的枚举在 C 或 C++ 中也不是概念,但是定义了 类型的有效桥接

  • 如果 T 是一个 FFI 安全的非可空指针类型,则保证 Option<T> 具有与 T 相同的布局和 ABI,因此也是 FFI 安全的。在撰写本文时,这涵盖了 &&mut 和函数指针,所有这些指针都永远不能为 null。

  • 元组结构体在 repr(C) 方面与结构体类似,因为与结构体的唯一区别是字段没有名称。

  • 对于无字段枚举,repr(C) 等价于 repr(u*) 之一(请参阅下一节)。选择的大小和符号是目标平台 C 应用程序二进制接口 (ABI) 的默认枚举大小和符号。请注意,C 中的枚举表示是实现定义的,所以这实际上是“最佳猜测”。特别是,当感兴趣的 C 代码使用某些标志编译时,这可能是错误的。

  • 带有 repr(C)repr(u*) 的无字段枚举仍然可能无法设置为没有相应变体的整数值,即使这在 C 或 C++ 中是允许的行为。构建与其任何变体都不匹配的枚举实例是未定义行为。(这允许继续编写和编译详尽的匹配,就像正常情况一样。)

repr(transparent)

#[repr(transparent)] 只能在具有单个非零大小字段(可能还有其他零大小字段)的结构体或单变体枚举上使用。其效果是保证整个结构体/枚举的布局和 ABI 与该单个字段相同。

注意:有一个 transparent_unions nightly 功能可以将 repr(transparent) 应用于联合体,但由于设计问题尚未稳定。有关更多详细信息,请参阅 跟踪问题

目标是使单个字段和结构体/枚举之间能够进行 transmute。这方面的一个例子是 UnsafeCell,它可以转换为它包装的类型 (UnsafeCell 还使用了不稳定的 no_niche,因此当嵌套在其他类型中时,它的 ABI 实际上不能保证相同)。

此外,保证在 FFI 中传递结构体/枚举时,另一端期望的内部字段类型有效。特别是,这对于 struct Foo(f32)enum Foo { Bar(f32) } 始终具有与 f32 相同的 ABI 是必需的。

仅当单个字段是 pub 或者其布局在文本中记录时,此 repr 才被视为类型公共 ABI 的一部分。否则,其他 crate 不应依赖该布局。

更多详细信息请参阅 RFC 1758RFC 2645

repr(u*),repr(i*)

这些指定大小和符号以生成无字段枚举。如果判别式溢出它必须放入的整数,它将产生编译时错误。你可以通过将溢出元素显式设置为 0 来手动要求 Rust 允许这样做。但是,Rust 不会允许你创建一个具有相同判别式的两个变体的枚举。

术语“无字段枚举”仅表示枚举的任何变体中都没有数据。没有 repr(u*)repr(C) 的无字段枚举仍然是 Rust 原生类型,并且没有稳定的 ABI 表示。添加 repr 会使其在 ABI 目的上被完全视为指定的整数类型。

如果枚举具有字段,则效果类似于 repr(C) 的效果,即类型具有定义的布局。这使得可以将枚举传递给 C 代码,或者访问类型的原始表示并直接操作其标签和字段。有关详细信息,请参阅 RFC

这些 repr 对结构体没有影响。

向具有字段的枚举添加显式的 repr(u*)repr(i*)repr(C) 会抑制空指针优化,如下所示

#![allow(unused)]
fn main() {
use std::mem::size_of;
enum MyOption<T> {
    Some(T),
    None,
}

#[repr(u8)]
enum MyReprOption<T> {
    Some(T),
    None,
}

assert_eq!(8, size_of::<MyOption<&u16>>());
assert_eq!(16, size_of::<MyReprOption<&u16>>());
}

此优化仍然适用于具有显式 repr(u*)repr(i*)repr(C) 的无字段枚举。

repr(packed)

repr(packed) 强制 Rust 删除任何填充,并将类型仅对齐到字节。这可能会改善内存占用,但也可能会带来其他负面副作用。

特别是,大多数架构强烈倾向于对齐值。这可能意味着未对齐的加载会受到惩罚 (x86),甚至会出错 (某些 ARM 芯片)。对于直接加载或存储打包字段等简单情况,编译器可能能够使用移位和掩码来掩盖对齐问题。但是,如果获取对打包字段的引用,则编译器不太可能能够生成代码来避免未对齐的加载。

由于这可能会导致未定义的行为,因此已实现 lint,并且它将成为硬错误。

不应轻易使用 repr(packed)。除非你有极端的要求,否则不应使用此选项。

此 repr 是 repr(C)repr(Rust) 的修饰符。

repr(align(n))

repr(align(n))(其中 n 是 2 的幂)强制类型至少具有 n 的对齐方式。

这使得可以实现一些技巧,例如确保数组的相邻元素永远不会彼此共享同一个缓存行(这可能会加速某些类型的并发代码)。

这是 repr(C)repr(Rust) 的修饰符。它与 repr(packed) 不兼容。