替代表示
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 1758 和 RFC 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)
不兼容。