备用表示

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++ 中的概念,但 定义了类型的有效桥接。

  • 如果 TFFI 安全的非空指针类型,则保证 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 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) 不兼容。