Description
Consider the following snippet:
package main
import (
"unsafe"
)
func main() {
bytes := [4]byte{1, 2, 3, 4}
ptr := unsafe.Pointer(&bytes[0])
ints := (*[1]int32)(ptr)
want := &[1]int32{0x04030201} // Assuming little endian.
println("Should be the same:", ints, want)
// Try modifying the original bytes.
ints[0] = 0x05060708
println("Got:", bytes[0], bytes[1], bytes[2], bytes[3])
println("Want:", 8, 7, 6, 5) // Assuming little endian.
}
When executed under native Go:
$ go run main.go
Should be the same: 0xc000034750 0xc000034754
Got: 8 7 6 5
Want: 8 7 6 5
When executed under GopherJS:
$ gopherjs run main.go
Should be the same: Uint8Array [ 1, 2, 3, 4 ] Int32Array [ 67305985 ]
Got: 8 2 3 4
Want: 8 7 6 5
The most important issue is in the last two lines: assigning the full int should have modified all 4 bytes, but only the least significant byte was actually changed. This can lead to significant correctness problems with the code which does low-level byte manipulation.
I think the root cause here is that the compiler provides special handling for converting an array-like type into unsafe.Pointer
, but it only returns the underlying typed array (e.g. Uint8Array
). It doesn't seem to provide complimentary handling of the opposite conversion. As a result, [n]byte
→ unsafe.Pointer
→ [n]byte
works ok, but [n]byte
→ unsafe.Pointer
→ [m]int32
does not, leading to obscure runtime errors.
A better solution is to extract the underlying ArrayBuffer
when converting to unsafe.Pointer
and wrap it into the correct typed array when converting back.