As they're value types, it never crossed my mind that they could go to the heap, but thinking about it just makes sense. The compiler rule is quite simple: All data stored on the stack must have a known, fixed size.
Therefore, data with an unknown size at compile time or a size that might change must be stored on the heap instead.
In the case for arrays for example, on the stack we put:
- A reference to the actual storage location (which will be on the heap)
- The array capacity and count properties
- Some metadata for the array type