I want to reinterpret a struct with a single interface as a struct with two pointer-sized ints.
My reading of the rules for unsafe.Pointer suggests that this is a valid usage: (*T2)(unsafe.Pointer(&T1{...})) where T1 and T2 share the same memory layout. However, I can’t help but wonder whether the garbage collector will get confused about what x really points to and consequently garbage-collect the contents of val.
The following code seems to work as intended, but is it safe?
package main
import (
"fmt"
"runtime"
"time"
"unsafe"
)
type Interface struct{ val interface{} }
type Uintptrs struct{ a, b uintptr }
func t() *Interface { return &Interface{val: "t=" + time.Now().String()} }
func main() {
var x *Uintptrs = (*Uintptrs)(unsafe.Pointer(t()))
runtime.GC()
y := (*Interface)(unsafe.Pointer(x))
fmt.Println(y.val)
}
This is valid and the garbage collector will be fine. But I don’t think there’s any guarantee that your two struct types will in fact have an equivalent memory layout and size. (Even if that’s the case at the moment.)
That’s a cool solution. I would probably just yolo it and test on each new Go release, for the very low risk that this ever changes & breaks. Your solution would at least guard against a uintptr magically becoming larger than an interface{}.
Thanks @calmh! I went one step further and added some compile-time assertions. At least, if anything does change, it won’t catch me off guard.
// Compile-time assert that the two types have the same size and alignment.
const _ = -uint(unsafe.Sizeof(Interface{}) ^ unsafe.Sizeof(SameLayout{}))
const _ = -uint(unsafe.Alignof(Interface{}) ^ unsafe.Alignof(SameLayout{}))
Ah yes, a [0]byte takes space when it’s the last element in a struct, otherwise a pointer to it would be a pointer outside the struct, potentially a pointer to the next struct if it was in array, and that’d be confusing.