包含Vec<i64>变体的枚举为何无额外内存开销?
嘿,这个问题问得特别到位!其实这背后是Rust编译器一个非常聪明的优化——空指针优化(Null Pointer Optimization),咱们一步步拆解来看就明白了。
先从你提到的IntEnum说起:它有两个变体,一个是啥数据都不带的Abc,另一个是装着i64的Int。i64本身占8字节,但为啥枚举的大小是16字节呢?
原因很简单:程序运行时需要知道当前枚举到底是Abc还是Int变体,所以编译器得加一个“标签”来区分两者。但i64的所有可能取值都是合法的(从-9223372036854775808到9223372036854775807全是有效的整数),编译器找不到任何一个特殊的i64值来代表Abc,只能额外分配一块内存存标签。又因为i64是8字节对齐的,标签也得跟着对齐到8字节,所以总大小就是8(标签)+8(i64数据)=16字节。
再看VecEnum的情况,它的另一个变体是Vec<i64>,占24字节(三个8字节字段:指向数据的指针、当前长度、容量)。为啥枚举大小和Vec<i64>完全一样?这就用到了空指针优化。
Rust里的Vec有个关键特性:它的内部指针永远不会是null——哪怕是空Vec,也不会用null指针,而是用一个特定的“悬垂指针”(比如指向全局的空内存区域)。也就是说,“指针为null,长度和容量都为0”这种状态,对于一个合法的Vec来说是完全不存在的。
编译器就抓住了这个“漏洞”:它把VecEnum::Abc直接表示成这种“无效的Vec状态”(指针null,len和cap都是0)。这样一来,程序运行时只要检查Vec的指针是不是null,就能判断当前是Abc还是Vec变体,根本不需要额外加标签!
说白了,编译器把区分变体的信息“藏”在了Vec本身的无效状态里,所以VecEnum的大小就和Vec<i64>完全一致,没有额外的内存开销。
总结一下核心逻辑:当枚举的一个变体是无数据的unit类型,另一个变体的类型存在至少一种“无效状态”时,编译器会自动启用空指针优化,用这个无效状态来表示unit变体,从而避免额外的标签内存。i64没有无效状态,所以必须加标签;Vec<i64>有无效状态(指针为null的情况),所以不需要额外开销。
备注:内容来源于stack exchange,提问作者Tadeo Hepperle

