title: Why do zero-sized types cause real allocations in some cases?
body: <p>I was playing with zero-sized types (ZSTs) as I was curious about how they are actually implemented under the hood. Given that ZSTs do not require any space in memory and taking a raw pointer is a safe operation, I was interested what raw pointers I would get from different kinds of ZST &quot;allocations&quot; and how weird (for safe Rust) the results would be.</p>
  <p>My first attempt (<code>test_stk.rs</code>) was to take const pointers to a few on-stack instances of ZSTs:</p>
  <pre class="lang-rust prettyprint-override"><code>struct Empty;
  struct EmptyAgain;
  
  fn main() {
      let stk_ptr: *const Empty = &amp;Empty;
      let stk_ptr_again: *const EmptyAgain = &amp;EmptyAgain;
      let nested_stk_ptr = nested_stk();
  
      println!(&quot;Pointer to on-stack Empty:        {:?}&quot;, stk_ptr);
      println!(&quot;Pointer to on-stack EmptyAgain:   {:?}&quot;, stk_ptr_again);
      println!(&quot;Pointer to Empty in nested frame: {:?}&quot;, nested_stk_ptr);
  }
  
  fn nested_stk() -&gt; *const Empty {
      &amp;Empty
  }
  </code></pre>
  <p>Compiling and running this produced the following result:</p>
  <pre class="lang-none prettyprint-override"><code>$ rustc test_stk.rs -o test_stk
  $ ./test_stk 
  Pointer to on-stack Empty:        0x55ab86fc6000
  Pointer to on-stack EmptyAgain:   0x55ab86fc6000
  Pointer to Empty in nested frame: 0x55ab86fc6000
  </code></pre>
  <p>A short analysis of the process memory map showed that <code>0x55ab86fc6000</code> was actually not a stack allocation, but the very beginning of the <code>.rodata</code> section. This seems logical: the compiler pretends that there is a single zero-sized value for each ZST, known at compile time, and each of these values resides in <code>.rodata</code>, as compile-time constants do.</p>
  <p>The second attempt was with boxed ZSTs (<code>test_box.rs</code>):</p>
  <pre class="lang-rust prettyprint-override"><code>struct Empty;
  struct EmptyAgain;
  
  fn main() {
      let ptr = Box::into_raw(Box::new(Empty));
      let ptr_again = Box::into_raw(Box::new(EmptyAgain));
      let nested_ptr = nested_box();
  
      println!(&quot;Pointer to boxed Empty:                 {:?}&quot;, ptr);
      println!(&quot;Pointer to boxed EmptyAgain:            {:?}&quot;, ptr_again);
      println!(&quot;Pointer to boxed Empty in nested frame: {:?}&quot;, nested_ptr);
  }
  
  fn nested_box() -&gt; *mut Empty {
      Box::into_raw(Box::new(Empty))
  }
  </code></pre>
  <p>Running this snippet gave:</p>
  <pre class="lang-none prettyprint-override"><code>$ rustc test_box.rs -o test_box
  $ ./test_box 
  Pointer to boxed Empty:                 0x1
  Pointer to boxed EmptyAgain:            0x1
  Pointer to boxed Empty in nested frame: 0x1
  </code></pre>
  <p>Quick debugging showed that this is how the allocator works for ZSTs (Rust's <code>liballoc/alloc.rs</code>):</p>
  <pre class="lang-rust prettyprint-override"><code>unsafe fn exchange_malloc(size: usize, align: usize) -&gt; *mut u8 {
      if size == 0 {
          align as *mut u8
      } else {
          // ...
      }
  }
  </code></pre>
  <p>The minimum possible alignment is 1 (as per the Nomicon), so for ZSTs the <code>box</code> operator calls <code>exchange_malloc(0, 1)</code> and the resulting address is <code>0x1</code>.</p>
  <p>After noticing that <code>into_raw()</code> returns a mutable pointer, I decided to retry the previous test (on-stack) with mutable pointers (<code>test_stk_mut.rs</code>):</p>
  <pre class="lang-rust prettyprint-override"><code>struct Empty;
  struct EmptyAgain;
  
  fn main() {
      let stk_ptr: *mut Empty = &amp;mut Empty;
      let stk_ptr_again: *mut EmptyAgain = &amp;mut EmptyAgain;
      let nested_stk_ptr = nested_stk();
  
      println!(&quot;Pointer to on-stack Empty:        {:?}&quot;, stk_ptr);
      println!(&quot;Pointer to on-stack EmptyAgain:   {:?}&quot;, stk_ptr_again);
      println!(&quot;Pointer to Empty in nested frame: {:?}&quot;, nested_stk_ptr);
  }
  
  fn nested_stk() -&gt; *mut Empty {
      &amp;mut Empty
  }
  </code></pre>
  <p>And running this printed the following:</p>
  <pre class="lang-none prettyprint-override"><code>$ rustc test_stk_mut.rs -o test_stk_mut
  $ ./test_stk_mut 
  Pointer to on-stack Empty:        0x7ffc3817b5e0
  Pointer to on-stack EmptyAgain:   0x7ffc3817b5f0
  Pointer to Empty in nested frame: 0x7ffc3817b580
  </code></pre>
  <p>It turns out that this time I had <strong>real stack-allocated values</strong>, each having its own address! When I tried to declare them sequentially (<code>test_stk_seq.rs</code>), I discovered that each of these values occupied <strong>eight</strong> bytes:</p>
  <pre class="lang-rust prettyprint-override"><code>struct Empty;
  
  fn main() {
      let mut stk1 = Empty;
      let mut stk2 = Empty;
      let mut stk3 = Empty;
      let mut stk4 = Empty;
      let mut stk5 = Empty;
  
      let stk_ptr1: *mut Empty = &amp;mut stk1;
      let stk_ptr2: *mut Empty = &amp;mut stk2;
      let stk_ptr3: *mut Empty = &amp;mut stk3;
      let stk_ptr4: *mut Empty = &amp;mut stk4;
      let stk_ptr5: *mut Empty = &amp;mut stk5;
  
      println!(&quot;Pointer to on-stack Empty: {:?}&quot;, stk_ptr1);
      println!(&quot;Pointer to on-stack Empty: {:?}&quot;, stk_ptr2);
      println!(&quot;Pointer to on-stack Empty: {:?}&quot;, stk_ptr3);
      println!(&quot;Pointer to on-stack Empty: {:?}&quot;, stk_ptr4);
      println!(&quot;Pointer to on-stack Empty: {:?}&quot;, stk_ptr5);
  }
  </code></pre>
  <p>Run:</p>
  <pre class="lang-none prettyprint-override"><code>$ rustc test_stk_seq.rs -o test_stk_seq
  $ ./test_stk_seq 
  Pointer to on-stack Empty: 0x7ffdba303840
  Pointer to on-stack Empty: 0x7ffdba303848
  Pointer to on-stack Empty: 0x7ffdba303850
  Pointer to on-stack Empty: 0x7ffdba303858
  Pointer to on-stack Empty: 0x7ffdba303860
  </code></pre>
  <p>So, here are the things I cannot understand:</p>
  <ol>
  <li><p>Why do boxed ZST allocations use the dumb <code>0x1</code> address instead of something more meaningful, like in case of &quot;on-stack&quot; values?</p>
  </li>
  <li><p>Why is there need to allocate real space for on-stack ZST values when there are mutable raw pointers to them?</p>
  </li>
  <li><p>Why are exactly eight bytes used for mutable on-stack allocations? Should I treat this size as &quot;0 bytes of actual type size + 8 bytes of alignment&quot;?</p>
  </li>
  </ol>