我搞不懂为什么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>>());
}

这将输出以下内容:

IntEnum: 16 bytes 
i64: 8 bytes      
VecEnum: 24 bytes 
Vec<i64>: 24 bytes

对于i64,它的行为与预期一样:使用i64变体的枚举需要额外的空间来编码枚举标记.但为什么VEC不是这样,它只由3个8字节值(PTR、LEN、容量)的堆栈内存组成?

谁能解释一下这里的内存布局是如何工作的,以及幕后发生了什么?

推荐答案

你这里有的是所谓的Option-like enum:

类似选项的枚举是包含2个变量的enum,其中:

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

(基本上,VecEnumOption<Vec<i64>>之间没有显著差异.)

进一步阅读上面的链接,我们可以看到编译器能够有效地使用有效负载类型(Vec<i64>)的"小众"(非法值)作为无有效负载变量的枚举值.这就是所谓的"鉴别式省略".

最明显、最广为人知的例子是Option<&T>.由于引用不能为空,因此零值是可用于存储None变体的利基.这使得&TOption<&T>的大小相同.

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

原始非空*mut T的包装器...

如果编译器知道Unique<T>不能为空,则该字段中的空指针是Vec<T>类型的利基,因此可以用作替代判别式,并执行判别式省略.

特别要注意的是,"全零"不是唯一有效的利基市场.可以使用作为有效载荷类型的非法值的任何位模式.例如,如果编译器知道类型包装f64保证包含的值不能是NaN,那么Option<f64>可以将None表示为NaN位模式.然而,"不允许空指针的类型中的空指针"很容易成为最常见的利基.

Rust相关问答推荐

Rust,polars CSV:有没有一种方法可以从impll BufRead(或任何字节迭代器)中读取CSV?

交叉术语未正确清除屏幕

在没有引用计数或互斥锁的情况下,可以从Rust回调函数内的封闭作用域访问变量吗?

值为可变对象的不可变HashMap

为什么std repeat trait绑定在impl块和关联函数之间?

如何实现泛型枚举的`Serde::Desialize`特性

支持TLS的模拟HTTP服务器

什么是`&;[][..]`铁 rust 里的刻薄?

通过异常从同步代码中产生yield 是如何工作的?

在 Rust 中忽略 None 值的正确样式

通过mem::transmute将数组展平安全吗?

我可以在 Rust 中 serde struct camel_case 和 deserde PascalCase

decltype、dyn、impl traits,重构时如何声明函数的返回类型

max(ctz(x), ctz(y)) 有更快的算法吗?

Rust Serde 为 Option:: 创建反序列化器

如何获取函数中borrow 的切片的第一部分?

使用 `clap` 在 Rust CLI 工具中设置布尔标志

tokio async rust 的 yield 是什么意思?

有没有办法隐藏类型定义?

在 Rust 中有条件地导入?