goreflectionunsafe-pointers

Copy slice with reflect and unsafe package


I understand that usage of unsafe package of golang is unsafe, but I'm doing it only for education purposes.

The idea is to copy a slice field of structure, copy it with reflect package and unsafe.pointer, modify and replace the original slice with new one.

And it looks like new slice is created and has correct capacity/length, and it contains an instance of GoodForSale type. But all fields of that instance (Name, Price and Qnty) have wrong values. So I suppose that I'm doing something wrong with pointers and getting garbade data:

package main

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

type GoodForSale struct {
    Name string
    Price int
    Qnty int
}

type Lead struct {
    ID int
    Name string
    ContactName string
    Budget int
    Items []GoodForSale
    DateTime string
    Status int
    Win bool
}

func main()  {
    lead := &Lead{
        ID:          41,
        Name:        "Phone",
        ContactName: "John Smith",
        Budget:      30000,
        Items:       []GoodForSale{
            {
                Name:  "Iphone 6",
                Price: 100,
                Qnty:  3,
            },
        },
        DateTime:    "12.08.2020 11:23:10",
        Status: 150,
        Win:         false,
    }


    //Change Items
    pt1 := unsafe.Pointer(&lead.Items)
    copyItems := &reflect.SliceHeader{
        Data: uintptr(pt1),
        Cap: cap(lead.Items),
        Len: len(lead.Items),
    }


    items := *(*[]GoodForSale)(unsafe.Pointer(copyItems))
    fmt.Println(items[0].Name)

}

It looks like I'm missing something about how pointers work here. But how can I make this idea work correct?

Here is a go playground url: https://play.golang.org/p/SKtJWe9RVEU


Solution

  • The problem is here:

    pt1 := unsafe.Pointer(&lead.Items) // pointer the slice, a.k.a "slice header"
    copyItems := &reflect.SliceHeader{
            Data: uintptr(pt1), // trying to use it as pointer to the actual data.
    

    "Data" needs a pointer to where the actual data starts, but you're giving a pointer to the slice itself.

    This is the correct way to get a pointer to the slice's data:

    pt1 := unsafe.Pointer(&lead.Items[0]) // pointer to the first element referenced by the slice
    

    Note that this will panic if len(lead.Items) == 0. In that case, you should use unsafe.Pointer(nil).

    You could also get the data pointer (along with len and cap) from the original slice by casting it as a reflect.SliceHeader like this:

    copyHeader := *(*reflect.SliceHeader)(unsafe.Pointer(&lead.Items))
    
    items := *(*[]GoodForSale)(unsafe.Pointer(&copyHeader))
    

    but at this point we've essentially just made a convoluted and "unsafe" version of this:

    items := lead.Items