DEV Community

birowo
birowo

Posted on

CONVERT BYTE SLICE TO AND FROM OTHER TYPES

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

//byte slice pointer to string pointer convertion
func bsPtrToStrPtr(bsPtr *[]byte) (strPtr *string) {
    return (*string)(unsafe.Pointer(bsPtr))
}

//string pointer to byte slice pointer convertion
func strPtrToBsPtr(strPtr *string) (bsPtr *[]byte) {
    bsPtr = (*[]byte)(unsafe.Pointer(strPtr))
    //the capacity of *bsPtr still not set
    //so it must be set with the length of *strPtr
    //setting its capacity must be done via reflection to reach their header
    (*reflect.SliceHeader)(unsafe.Pointer(bsPtr)).Cap = (*reflect.SliceHeader)(unsafe.Pointer(strPtr)).Len
    return
}

//byte slice pointer to int64 pointer convertion
func bsPtrToInt64Ptr(bsPtr *[]byte) (int64Ptr *int64) {
    return (*int64)(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(bsPtr)).Data))
}

//int64 pointer to byte slice pointer convertion
func int64PtrToBsPtr(int64Ptr *int64, bsPtr *[]byte) {
    //int64 does not have header like string, so we can not do like this :
    //bsPtr = (*[]byte)(unsafe.Pointer(int64Ptr)
    //like on string convertion above
    //if we do that, the SliceHeader.Data of *bsPtr will contain
    //value (*int64Ptr), not pointer (int64Ptr) as it should be

    slcHdr := (*reflect.SliceHeader)(unsafe.Pointer(bsPtr))
    slcHdr.Data = uintptr(unsafe.Pointer(int64Ptr))
    slcHdr.Len = 8 //we know that the size of int64 is 8 byte
    slcHdr.Cap = 8
}

//reverse byte slice
func reverse(bs []byte) {
    for i, j := 0, len(bs)-1; i < j; i, j = i+1, j-1 {
        bs[i], bs[j] = bs[j], bs[i]
    }
}

func main() {
    bs := []byte("test 123")
    strPtr := bsPtrToStrPtr(&bs)
    fmt.Printf("byte slice: %s, type: %T; string: %s, type: %T\n", bs, bs, *strPtr, *strPtr)

    //be noticed that if we change bs value(must have same length with original value),
    //then the value of *strPtr also changed
    bs = []byte("123 test")
    fmt.Printf("byte slice: %s, type: %T; string: %s, type: %T\n", bs, bs, *strPtr, *strPtr)
    //and vice versa
    *strPtr = "wertyuio"
    fmt.Printf("byte slice: %s, type: %T; string: %s, type: %T\n", bs, bs, *strPtr, *strPtr)
    fmt.Println("byte slice, length:", len(bs), "& capacity:", cap(bs), ";string length:", len(*strPtr))
    fmt.Println()

    str := "asdfghjkl"
    bsPtr := strPtrToBsPtr(&str)
    fmt.Printf("byte slice: %s, type: %T; string: %s, type: %T\n", *bsPtr, *bsPtr, str, str)

    //be noticed that if we change str value(must have same length with original value),
    //then the value of *bsPtr also changed
    str = "lkjhgfdsa"
    fmt.Printf("byte slice: %s, type: %T; string: %s, type: %T\n", *bsPtr, *bsPtr, str, str)
    //and vice versa
    *bsPtr = []byte("1234S6789")
    fmt.Printf("byte slice: %s, type: %T; string: %s, type: %T\n", *bsPtr, *bsPtr, str, str)
    fmt.Println("byte slice, length:", len(*bsPtr), "& capacity:", cap(*bsPtr), ";string length:", len(str))
    fmt.Println()

    //in slice, int64 allocation size is 8 bytes, index-0 hold LSB, index-7 hold MSB
    //LSB: least significant byte mean if we change that byte will only change little amount of the value
    //MSB: most significant byte mean the opposite of that I mentioned above
    bs = []byte{0x15, 0x81, 0xe9, 0x7d, 0xf4, 0x10, 0x22, 0x11}
    int64Ptr := bsPtrToInt64Ptr(&bs)
    //if we print byte slice, it will be displayed as how we write it on code
    //from index-0 will be displayed on leftmost, to last index will be displayed on rightmost
    fmt.Printf("byte slice: %x, type: %T\n", bs, bs)
    //however as int64 value, it will print MSB on leftmost and LSB on rightmost
    fmt.Printf("int64(hex): %x, type: %T\n", *int64Ptr, *int64Ptr)
    //bs = []byte{0xb1, 0x1c, 0x6c, 0xb1, 0xf4, 0x10, 0x22, 0x11}
    *int64Ptr = 1234567890987654321
    //if we change the int64 value, the byte slice value will also change
    fmt.Printf("byte slice: %x, type: %T\n", bs, bs)
    fmt.Printf("int64(hex): %x, type: %T\n", *int64Ptr, *int64Ptr)
    fmt.Println()

    _int64 := int64(1234543210987656789)
    int64PtrToBsPtr(&_int64, &bs)
    fmt.Printf("byte slice: %x, type: %T\n", bs, bs)
    fmt.Printf("int64(hex): %x, type: %T\n", _int64, _int64)
    _int64 = int64(1231231230321321321)
    //bs = []byte{0x69, 0x65, 0xab, 0xd7, 0x47, 0x36, 0x16, 0x11}
    fmt.Printf("byte slice: %x, type: %T\n", bs, bs)
    fmt.Printf("int64(hex): %x, type: %T\n", _int64, _int64)
    fmt.Println()

    //other thing that also we must pay attention is about endianness (big endian & little endian)
    //i found this article that might be worth it to read :
    //https://www.freecodecamp.org/news/what-is-endianness-big-endian-vs-little-endian/
    //to correct the endianness maybe we need byte slice reversal function :
    bs = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}
    fmt.Printf("before reverse process: %x\n", bs)
    reverse(bs)
    fmt.Printf("after reverse process: %x\n", bs)
}
Enter fullscreen mode Exit fullscreen mode

build output :
bytesliceconversion_go

playground : https://play.golang.org/p/TCvWw1mf4kG

Discussion (0)