我很困惑为什么以下代码中 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)组成?
有人可以解释一下内存布局是如何工作的以及幕后发生了什么吗?
这里有一个所谓的类似 Option 的枚举:
类似选项的枚举是一个 2 变体,
enum其中:
- 没有
enum明确的#[repr(...)],并且- 一种变体只有一个字段,并且
- 另一个变体没有字段(“单位变体”)。
VecEnum(基本上,和之间没有显着差异Option<Vec<i64>>。)
进一步阅读上面的链接,我们可以看到编译器能够有效地使用有效负载类型 ( Vec<i64>) 的“利基”(非法值)作为无有效负载变体的枚举值。这称为“判别式省略”。
最明显和众所周知的例子是Option<&T>。由于引用不能为空,因此零值是可用于存储None变体的利基。这使得&T和Option<&T>具有相同的尺寸。
同样的事情也发生在这里。的第一个字段Vec<T>是RawVec<T>(内部类型),其第一个字段是称为的(文档隐藏)类型Unique<T>:
围绕原始非空的包装
*mut T...
好吧,如果编译器知道Unique<T>不能为空,那么该字段中的空指针就是该Vec<T>类型的一个利基,因此可以将其用作替代判别式,并执行判别式省略。
请特别注意,“全零”并不是唯一有效的利基。任何对于有效负载类型来说是非法值的位模式都可以使用。例如,如果编译器知道类型包装f64可以保证所包含的值不能是NaN,则Option<NotNanF64>可以表示None为NaN位模式。然而,“不允许空指针的类型中的空指针”很容易成为最常见的利基市场。