Storage of Option<i32> in memory

in my rust programming exam i found this question can you help me define the last two blocks, i figured out that the first 24 bytes are for the vec (pointer,capacity,len) but the last two? mind that u should read the block like : 308a6e01 00600000// 03000000 00000000// and so on
Within a program, the following data structure is defined:

struct Bucket {
    data: Vec<i32>,
    threshold: Option<i32>,
}

Using the debugger, it has been determined that, for an instance of Bucket, it is stored at the address 0x00006000014ed2c0. Observing the memory at this address, the following content is shown (in blocks of 32 bits):

308a6e01 00600000 03000000 00000000 03000000 00000000 01000000 0a000000

What can be inferred about the values contained in the various fields of the individual instance?

The representation of Option<i32> in memory is potentially subject to change in the future (i.e. it’s not guaranteed to be stable) but at the moment it looks something like

01000000 <= 32-bit discriminant (tag) valued 0 for None, or 1 for Some
0a000000 <= this memory is unused in the None case, and contains the i32 in the Some case

The integers being represented in little-endian order, the 01000000 is the value 1 for “Some” and the contents 0a000000 are the number 10, so Some(10).

The choice of discriminant (tag) size in Rust enums is automatic… generally only as small as possible, so for 2 variants in Option, it would perhaps only be a u8, but then it would leave 3 bytes unused as padding. So it seems like it just uses up the whole space for the discriminant, which helps e.g. leaving more niches.

I’m not sure what the best link about Option’s niche optimization is, but it has effects such as making Option<Option<i32>> more compact by re-using the inner Option’s tag so that it’s still only 8 bytes long, consisting of a

  • 32-bit discriminant (tag) valued 0 for Some(None), or 1 for Some(Some(…)), or 2 for None
  • another 32 bits, unused in the Some(None) and None case, and containing the i32 in the Some(Some(…)) case

and this even scales up to much more nested usage of Option like this

use core::mem;
fn main() {
    dbg!(
        mem::size_of::<i32>(),
        mem::size_of::<Option<i32>>(),
        mem::size_of::<Option<Option<i32>>>(),
        mem::size_of::<Option<Option<Option<i32>>>>(),
        mem::size_of::<Option<Option<Option<Option<i32>>>>>(),
        mem::size_of::<Option<Option<Option<Option<Option<i32>>>>>>(),
        mem::size_of::<Option<Option<Option<Option<Option<Option<i32>>>>>>>(),
        mem::size_of::<Option<Option<Option<Option<Option<Option<Option<i32>>>>>>>>(),
        mem::size_of::<Option<Option<Option<Option<Option<Option<Option<Option<i32>>>>>>>>>(),
        mem::size_of::<Option<Option<Option<Option<Option<Option<Option<Option<Option<i32>>>>>>>>>>(),
        mem::size_of::<Option<Option<Option<Option<Option<Option<Option<Option<Option<Option<i32>>>>>>>>>>>(),
    );
}
[src/main.rs:3] mem::size_of::<i32>() = 4
[src/main.rs:3] mem::size_of::<Option<i32>>() = 8
[src/main.rs:3] mem::size_of::<Option<Option<i32>>>() = 8
[src/main.rs:3] mem::size_of::<Option<Option<Option<i32>>>>() = 8
[src/main.rs:3] mem::size_of::<Option<Option<Option<Option<i32>>>>>() = 8
[src/main.rs:3] mem::size_of::<Option<Option<Option<Option<Option<i32>>>>>>() = 8
[src/main.rs:3] mem::size_of::<Option<Option<Option<Option<Option<Option<i32>>>>>>>() = 8
[src/main.rs:3] mem::size_of::<Option<Option<Option<Option<Option<Option<Option<i32>>>>>>>>() = 8
[src/main.rs:3] mem::size_of::<Option<Option<Option<Option<Option<Option<Option<Option<i32>>>>>>>>>() = 8
[src/main.rs:3] mem::size_of::<Option<Option<Option<Option<Option<Option<Option<Option<Option<i32>>>>>>>>>>() = 8
[src/main.rs:3] mem::size_of::<Option<Option<Option<Option<Option<Option<Option<Option<Option<Option<i32>>>>>>>>>>>() = 8
6 Likes