TL;DR:最好使用Node2,因为它可以避免在next 是None 的情况下进行堆分配。
为了进一步解释,让我们将问题简化为:Option<Box> 或 Box<Option> 更好吗?
首先要注意的是,两种变体都只存储一个指针:
type VoidPtr = *const ();
let pointer_size = mem::size_of::<VoidPtr>();
assert_eq!(mem::size_of::<Option<Box<i32>>>(), pointer_size);
assert_eq!(mem::size_of::<Box<Option<i32>>>(), pointer_size);
这对于Box<Option> 来说并不奇怪,因为它实际上是一个指向Option 的指针。但是使用Box<Option>的缺点是即使值为None,Box也会进行堆分配。
// Although the Option is None, the pointer is not null,
// so a heap allocation is done to store None.
let none_box_option: Box<Option<i32>> = Box::new(None);
let pointer = &*none_box_option as *const Option<i32>;
assert!(pointer.is_null().not());
但是,Option<Box> 也只使用一个指针而不是例如一个指针加上一个布尔值来指示包含的值是Some 还是None,这可能会令人惊讶。
这里使用的技巧(至少在发布模式下)称为null pointer optimization。空指针用于指示None。每个其他值都是指定内存地址处的Some。这导致在值为 None 的情况下不必进行堆分配。相反,指针将设置为 null。
// A Option<Box> indicates None through a null pointer.
let none_option_box: Option<Box<i32>> = None;
assert!(as_bytes(&none_option_box).iter().all(|&byte| byte == 0));
// A Some Option<Box> simply holds the pointer to the inner value.
let some_option_box: Option<Box<i32>> = Some(Box::new(42));
let inner_bytes = as_bytes(&some_option_box);
let pointer = some_option_box.as_ref().unwrap().as_ref() as *const i32;
let pointer_bytes = as_bytes(&pointer);
assert_eq!(inner_bytes, pointer_bytes);
回到最初的问题:观察到的行为转移到Node 和Node2。两者都需要相同数量的内存。
assert_eq!(mem::size_of::<Node<()>>(), mem::size_of::<Node2<()>>());
assert_eq!(mem::size_of::<Node<u64>>(), mem::size_of::<Node2<u64>>());
assert_eq!(mem::size_of::<Node<String>>(), mem::size_of::<Node2<String>>());
但是,如果next 是None,那么Node 将进行堆分配,而Node2 则不会。因此我建议使用Node2。
Full code example