Convert between slices of different types

Viewed 64070

I get a byte slice ([]byte) from a UDP socket and want to treat it as an integer slice ([]int32) without changing the underlying array, and vice versa. In C(++) I would just cast between pointer types; how would I do this in Go?

10 Answers

Since Go 1.17, there is a simpler way to do this using the unsafe package.

import (
    "unsafe"
)

const SIZEOF_INT32 = unsafe.Sizeof(int32(0)) // 4 bytes

func main() {
    var bs []byte
    
    // Do stuff with `bs`. Maybe do some checks ensuring that len(bs) % SIZEOF_INT32 == 0
    
    data := unsafe.Slice((*int32)(unsafe.Pointer(&bs[0])), len(bs)/SIZEOF_INT32)

    // A more verbose alternative requiring `import "reflect"`
    // data := unsafe.Slice((*int32)(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&bs)).Data)), len(bs)/SIZEOF_INT32)
}

Go 1.17 and beyond

Go 1.17 introduced the unsafe.Slice function, which does exactly this.

Converting a []byte to a []int32:

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    theBytes := []byte{
        0x33, 0x44, 0x55, 0x66,
        0x11, 0x22, 0x33, 0x44,
        0x77, 0x66, 0x55, 0x44,
    }

    numInts := uintptr(len(theBytes)) * unsafe.Sizeof(theBytes[0]) / unsafe.Sizeof(int32(0))
    theInts := unsafe.Slice((*int32)(unsafe.Pointer(&theBytes[0])), numInts)

    for _, n := range theInts {
        fmt.Printf("%04x\n", n)
    }
}

Playground.

You can do it with the "unsafe" package

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var b [8]byte = [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
    var s *[4]uint16 = (*[4]uint16)(unsafe.Pointer(&b))
    var i *[2]uint32 = (*[2]uint32)(unsafe.Pointer(&b))
    var l *uint64 = (*uint64)(unsafe.Pointer(&b))

    fmt.Println(b)
    fmt.Printf("%04x, %04x, %04x, %04x\n", s[0], s[1], s[2], s[3])
    fmt.Printf("%08x, %08x\n", i[0], i[1])
    fmt.Printf("%016x\n", *l)
}

/*
 * example run:
 * $ go run /tmp/test.go
 * [1 2 3 4 5 6 7 8]
 * 0201, 0403, 0605, 0807
 * 04030201, 08070605
 * 0807060504030201
 */

Perhaps it was not available when the earlier answers were given, but it would seem that the binary.Read method would be a better answer than "the right way" given above.

This method allows you to read binary data from a reader directly into the value or buffer of your desired type. You can do this by creating a reader over your byte array buffer. Or, if you have control of the code that is giving you the byte array, you can replace it to read directly into your buffer without the need for the interim byte array.

See https://golang.org/pkg/encoding/binary/#Read for the documentation and a nice little example.

func crackU32s2Bytes(us []uint32) []byte {
    var bs []byte
    var ptrBs = (*reflect.SliceHeader)(unsafe.Pointer(&bs))
    var ptrUs = (*reflect.SliceHeader)(unsafe.Pointer(&us))
    ptrBs.Data = ptrUs.Data
    ptrBs.Len = ptrUs.Len*4
    ptrBs.Cap = ptrBs.Len
    return bs
}

func crackBytes2U32s(bs []byte) []uint32 {
    var us []uint32
    var ptrBs = (*reflect.SliceHeader)(unsafe.Pointer(&bs))
    var ptrUs = (*reflect.SliceHeader)(unsafe.Pointer(&us))
    ptrUs.Data = ptrBs.Data
    ptrUs.Len = ptrBs.Len/4
    ptrUs.Cap = ptrUs.Len
    return us
}
Related