为什么 Nil 会增加一个枚举大小而另一个类型不会?Rust 枚举类型的内存分配是怎样的?

4
如果我定义以下枚举类型,Nil不会增加枚举的大小:
  use std::mem::size_of;

  enum Foo {
    Cons(~char)
  }

  enum Bar {
    Cons(~char),
    Nil
  }

  println!("{}", size_of::<Foo>());
  println!("{}", size_of::<Bar>());

  // -> 4
  // -> 4

另一方面:
  enum Foo {
    Cons(char)
  }

  enum Foo {
    Cons(char),
    Nil
  }

产生:

  // -> 4
  // -> 8

当我定义一个枚举时,会发生什么?这些结构的内存是如何分配的?


1
可能是因为第一个可以表示为指向值和空指针的指针(因此它的大小是一个指针),只是猜测。 - Arjan
你有在 GitHub 上检查 Rust 的源代码吗?那会给你他们的具体实现。我不是很确定,但那应该是我首先要检查的地方。 - Brendan Lesniak
1个回答

10
一种天真的枚举方法是为其最大变量的内容分配足够的空间,再加上一个描述符。这是一个标准的“标记联合”。
Rust比这更聪明一些。(它可以更聪明,但目前还不是)它知道,在给定一个`~T`的情况下,至少有一个值不能在那个内存位置:零。因此,在像你的`enum { Cons(~T), Nil }`这样的情况下,它能够将其优化为一个字,任何非零值在内存中表示`Cons(〜T)`,而零值在内存中表示`Nil`。
当你处理`char`时,这种优化就无法发生:零是一个有效的代码点。恰好,`char`被定义为Unicode码点,所以实际上可以将该变量优化到该空间中,末尾有足够的备用位(Unicode字符只需要21位,因此在32位空间中我们有11个备用位)。这说明了Rust的枚举判别优化目前并不是特别聪明。

1
这是一个很棒的答案。值得一提的是,仅仅出于好玩,在2019年,这个问题已经不存在了;我猜 Rust 的枚举判别式优化在几年内变得更好了。=) - River Tam
1
不错!我不知道 Rust 现在可以使用无效的 char 值作为鉴别器!确认一下:enum Bar2 { Cons(char), Nil } println!("{:x}", unsafe { std::mem::transmute::<_, u32>(Bar2::Nil) }); 输出 110000。理解方式是这样的:Unicode 定义了最高到 U+10FFFF,明确声明超过该值的值永远无效;因此 Rust 认为超过该点的任何内容都可以作为鉴别器值,并使用下一个值。 - Chris Morgan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接