备用表示
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++ 中空类型的行为(即它们应该仍然占用一个字节的空间)明确相反。
-
DST 指针(宽指针)和元组不是 C 语言中的概念,因此永远不是 FFI 安全的。
-
具有字段的枚举也不是 C 或 C++ 中的概念,但 定义了类型的有效桥接。
-
如果
T
是 FFI 安全的非空指针类型,则保证Option<T>
具有与T
相同的布局和 ABI,因此也是 FFI 安全的。 在撰写本文时,这涵盖了&
、&mut
和函数指针,所有这些指针都不能为 null。 -
元组结构体在
repr(C)
方面类似于结构体,因为与结构体的唯一区别是字段没有命名。 -
对于无字段枚举,
repr(C)
等效于repr(u*)
之一(参见下一节)。 所选大小是目标平台的 C 应用程序二进制接口 (ABI) 的默认枚举大小。 请注意,C 语言中的枚举表示是实现定义的,因此这实际上是一个“最佳猜测”。 特别是,当使用某些标志编译相关的 C 代码时,这可能是不正确的。 -
即使在 C 或 C++ 中允许这种行为,但没有对应变体的
repr(C)
或repr(u*)
的无字段枚举仍然不能设置为整数值。 (不安全地)构造与其任何变体都不匹配的枚举实例是未定义的行为。 (这允许继续像往常一样编写和编译穷举匹配。)
repr(transparent)
#[repr(transparent)]
只能用于具有单个非零大小字段的结构体或单变体枚举(可能还有其他零大小字段)。 其效果是保证整个结构体/枚举的布局和 ABI 与该字段相同。
注意:有一个
transparent_unions
nightly 功能可以将repr(transparent)
应用于联合,但由于设计问题,它尚未稳定。 有关更多详细信息,请参阅跟踪问题。
目标是使在单个字段和结构体/枚举之间进行转移成为可能。 这方面的一个例子是 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)
不兼容。