为什么具有 Vec<i64> 变体的枚举没有内存开销?

Tad*_*rle 2 memory enums rust

我很困惑为什么以下代码中 Vec<i64> 和 VecEnum 的大小相同:

pub enum VecEnum {
    Abc,
    Vec(Vec<i64>),
}

pub enum IntEnum {
    Abc,
    Int(i64),
}

pub fn main() {
    println!("IntEnum: {} bytes", core::mem::size_of::<IntEnum>());
    println!("i64: {} bytes", core::mem::size_of::<i64>());
    println!("VecEnum: {} bytes", core::mem::size_of::<VecEnum>());
    println!("Vec<i64>: {} bytes", core::mem::size_of::<Vec<i64>>());
}
Run Code Online (Sandbox Code Playgroud)

输出如下:

IntEnum: 16 bytes 
i64: 8 bytes      
VecEnum: 24 bytes 
Vec<i64>: 24 bytes
Run Code Online (Sandbox Code Playgroud)

对于 i64,它的行为符合预期:具有 i64 变体的枚举需要额外的空间来编码枚举标记。但为什么 Vec 的情况并非如此,它仅由堆栈内存的 3 个 8 字节值(ptr、len、capacity)组成?

有人可以解释一下内存布局是如何工作的以及幕后发生了什么吗?

cdh*_*wie 8

这里有一个所谓的类似 Option 的枚举

类似选项的枚举是一个 2 变体,enum其中:

  • 没有enum明确的#[repr(...)],并且
  • 一种变体只有一个字段,并且
  • 另一个变体没有字段(“单位变体”)。

VecEnum(基本上,和之间没有显着差异Option<Vec<i64>>。)

进一步阅读上面的链接,我们可以看到编译器能够有效地使用有效负载类型 ( Vec<i64>) 的“利基”(非法值)作为无有效负载变体的枚举值。这称为“判别式省略”。

最明显和众所周知的例子是Option<&T>。由于引用不能为空,因此零值是可用于存储None变体的利基。这使得&TOption<&T>具有相同的尺寸。

同样的事情也发生在这里。的第一个字段Vec<T>RawVec<T>(内部类型),其第一个字段是称为的(文档隐藏)类型Unique<T>

围绕原始非空的包装*mut T...

好吧,如果编译器知道Unique<T>不能为空,那么该字段中的空指针就是该Vec<T>类型的一个利基,因此可以将其用作替代判别式,并执行判别式省略。

请特别注意,“全零”并不是唯一有效的利基。任何对于有效负载类型来说是非法值的位模式都可以使用。例如,如果编译器知道类型包装f64可以保证所包含的值不能是NaN,则Option<NotNanF64>可以表示NoneNaN位模式。然而,“不允许空指针的类型中的空指针”很容易成为最常见的利基市场。

  • 该死的,谁让 Rust 编译器比我聪明。 (3认同)