Rust的表示(repr(Rust))

首先,所有类型都有以字节为单位指定的对齐方式.类型的对齐指定哪些地址有效以存储值.具有对齐n的值只能存储在n的倍数的地址中.因此,对齐2意味着你必须存储在偶数地址,1意味着你可以存储在任何地方.对齐至少为1,并且总是2的幂.

基元通常与它们的大小一致,尽管这是特定于平台的行为.特别是,在x86上,u64f64通常对齐到4个字节(32位).

类型的大小必须始终是其对齐的倍数(0是任何对齐的有效大小).这可以确保始终通过偏移其大小的倍数来索引该类型的数组.请注意,在动态大小类型的情况下,可能不会静态地知道类型的大小和对齐.

Rust为你提供了以下布置复合数据的方法:

  • 结构(命名积类型)

  • 元组(匿名积类型)

  • 数组(同类积类型)

  • 枚举(命名和类型—标记联合)

  • 联合(无标记联合)

如果没有任何变体具有关联数据,则称枚举为 无字段(field-less) .

默认情况下,复合结构的对齐方式等于其字段对齐的最大值.因此,Rust将在必要时插入填充,以确保所有字段都正确对齐,并且整体类型的大小是其对齐的倍数.例如:

  1. struct A {
  2. a: u8,
  3. b: u32,
  4. c: u16,
  5. }

将在这些基元与各自大小对齐的架构上进行32位对齐.因此整个结构的大小是32位的倍数.它可能会成为:

  1. struct A {
  2. a: u8,
  3. _pad1: [u8; 3], // to align `b`
  4. b: u32,
  5. c: u16,
  6. _pad2: [u8; 2], // to make overall size multiple of 4
  7. }

或可能:

  1. struct A {
  2. b: u32,
  3. c: u16,
  4. a: u8,
  5. _pad: u8,
  6. }

这些类型 没有间接性(no indirection) ;所有数据都存储在结构中,正如你在C中所期望的那样.但是除了数组(它密集且按顺序排列)之外,默认情况下不指定数据布局.给出以下两个结构定义:

  1. struct A {
  2. a: i32,
  3. b: u64,
  4. }
  5. struct B {
  6. a: i32,
  7. b: u64,
  8. }

Rust 确实(does) 确保两个A的实例的数据布局以完全相同的方式.但是,Rust目前 不(does not) 保证A的实例具有与B的实例相同的字段排序或填充.

如A和B所写,这一点似乎很迂腐,但Rust的其他几个特性使得语言可以以复杂的方式使用数据布局.

例如,考虑这个结构:

  1. struct Foo<T, U> {
  2. count: u16,
  3. data1: T,
  4. data2: U,
  5. }

现在考虑Foo<u32, u16>Foo<u16, u32>的单形态.如果Rust以指定的顺序排列字段,我们希望它填充结构中的值以满足它们的对齐要求.因此,如果Rust没有重新排序字段,我们希望它产生以下内容:

  1. struct Foo<u16, u32> {
  2. count: u16,
  3. data1: u16,
  4. data2: u32,
  5. }
  6. struct Foo<u32, u16> {
  7. count: u16,
  8. _pad1: u16,
  9. data1: u32,
  10. data2: u16,
  11. _pad2: u16,
  12. }

后一种情况只会浪费空间.因此,空间的最佳使用需要不同的单形态以具有 不同的字段排序(different field orderings) .

枚举使这一考虑变得更加复杂.天真的,如:

  1. enum Foo {
  2. A(u32),
  3. B(u64),
  4. C(u8),
  5. }

可能被布局为:

  1. struct FooRepr {
  2. data: u64, // this is either a u64, u32, or u8 based on `tag`
  3. tag: u8, // 0 = A, 1 = B, 2 = C
  4. }

事实上,这大概就是它的布局方式(以tag的大小和位置为模).

然而,在某些情况下,这种表示效率低下.典型的例子是Rust的”空指针优化(null pointer optimization)”:由单个外部单元变体(例如None)和(可能嵌套的)非可空指针(non-nullable pointer)变体(例如Some(&T))组成的枚举使得标签不必要,因为空指针值可以安全地解释为单元(None)变体.最终结果是,例如,size_of::<Option<&T>>() == size_of::<&T>().

Rust中有许多类型就是或包含非可空指针,如Box<T>,Vec<T>,String,&T&mut T.同样,可以想象嵌套枚举将其标签汇集到单个判别式中,因为根据定义它们具有有限范围的有效值.原则上,枚举可以使用相当精细的算法来存储具有禁止值的嵌套类型中的位.因此,特别(especially)希望我们今天不要指定枚举布局.