I found this trick recently to replicate a C union in Go with unsafe.Sizeof, arrays, and some constant arithmetic. This lets you stack-allocate data that needs to be the max size of multiple other data types. So given a type
A and
B that you want to union, you can say
const ASize = int64(unsafe.Sizeof(*new(A)))
const BSize = int64(unsafe.Sizeof(*new(B)))
const diff = ASize - BSize
const max = ASize - ((diff>>(bits.UintSize-1))&1)*diff
var storage [max]byte
*(*A)(unsafe.Pointer(&storage))
*(*B)(unsafe.Pointer(&storage))
This process can be repeated for any number of types. For example, if you wanted to represent a tagged union defined like
type A =
| B
| C int
| D (a: int, b: int)
you could lower it to something like
const size = int64(unsafe.Sizeof(*new(B)))
const size0 = int64(unsafe.Sizeof(*new(C)))
const diff = size - size0
const max = size - diff*((diff>>(bits.UintSize-1))&1)
const size1 = int64(unsafe.Sizeof(*new(D)))
const diff0 = size1 - max
const max0 = size1 - diff0*((diff0>>(bits.UintSize-1))&1)
type A struct {
tag uint8
storage [max0]byte
}
type B struct{}
type C int
type D struct {
a int
b int
}
Unfortunately, the resulting code is pretty inefficient compared to interfaces or struct embedding. It might be that the use of unsafe is hindering compiler optimizations, or maybe it's an alignment issue.