The issue is struct padding and/or array padding. Despite the fact that a Tuple{UInt8, UInt32}
could fit in 5 btyes, it actually uses 8 in most situations.
julia> sizeof(Tuple{UInt8, UInt32})
8
julia> fieldoffset(Tuple{UInt8, UInt32}, 1)
0x0000000000000000
julia> fieldoffset(Tuple{UInt8, UInt32}, 2)
0x0000000000000004
So the layout is
[ UInt8 UNUSED UNUSED UNUSED UInt32__UInt32__UInt32__UInt32 ]
My recommendation is to use a Tuple{UInt32, UInt32}
instead. At the end of the day it has the same layout because of the padding.
julia> reinterpret(UInt8, Tuple{UInt32, UInt32}[(1, 2)])
8-element reinterpret(UInt8, ::Vector{Tuple{UInt32, UInt32}}):
0x01
0x00
0x00
0x00
0x02
0x00
0x00
0x00
You can
julia> reinterpret(NTuple{5, UInt8}, (0x1, 0x00002))
(0x01, 0x02, 0x00, 0x00, 0x00)
but I can’t explain exactly why this uses a different layout. Probably because it never actually uses memory, it’s just two values that are in registers anyway so it just mashes them together.
Knowing what we know about padding, we get a little closer with
julia> reinterpret(NTuple{8,UInt8}, Tuple{UInt8, UInt32}[(1, 2)])
1-element reinterpret(NTuple{8, UInt8}, ::Vector{Tuple{UInt8, UInt32}}):
Error showing value of type Base.ReinterpretArray{NTuple{8, UInt8}, 1, Tuple{UInt8, UInt32}, Vector{Tuple{UInt8, UInt32}}, false}:
ERROR: Padding of type NTuple{8, UInt8} is not compatible with type Tuple{UInt8, UInt32}.
which does manage to make the array, but accessing it fails (hence Error showing value
).
There are more nefarious ways to make this work BUT YOU REALLY SHOULD NOT DO THEM BECAUSE THEY CAN CAUSE WILD AND CRAZY BUGS.
(they look something like this, but I'm no expert and this is a BAD IDEA(tm) anyway)
julia> X = [(0x1, 0x00002)]
1-element Vector{Tuple{UInt8, UInt32}}:
(0x01, 0x00000002)
julia> unsafe_wrap(Vector{UInt8}, Ptr{UInt8}(pointer(X)), sizeof(X))
8-element Vector{UInt8}:
0x01
0x5b # garbage
0xcd # garbage
0x98 # garbage
0x02
0x00
0x00
0x00