3

I am trying to learn how pointers work on go. Why is the following example not working?

package main

import (    
    "fmt"
    "unsafe"
)

type SliceOfStrings []string

// function that creates an slice of []string
// returns interface{} cause I am interested on learning how pointers work
func Create() interface{} {
    var mySlice1 SliceOfStrings = make([]string, 0)
    mySlice1 = append(mySlice1, "str1")
    mySlice1 = append(mySlice1, "str2")

    // return a slice with as ["str1","str2"]
    return mySlice1
}

func main() {

    x := Create()
    // 0xc000021940
    fmt.Printf("address of x is %p \n", &x)

    // get unsafe pointer to address of x

    // unsafe pointer. Prints 0xc000021940
    p1 := unsafe.Pointer(&x)
    fmt.Println(p1)

    // unsigned pointer. Prints 824633858368
    p2 := uintptr(p1)
    fmt.Println(p2)

    // prints same value as p1 0xc000021940
    p3 := unsafe.Pointer(p2)
    fmt.Println(p3)

    // Make p4 point to same address as 0xc000021940
    p4 := (*SliceOfStrings)(p3)
    //fmt.Println(p4)

    // why this does not print "str1" ??
    fmt.Println((*p4)[0])

    // I get error: runtime error: invalid memory address or nil pointer dereference
}

icza
  • 389,944
  • 63
  • 907
  • 827
Tono Nam
  • 34,064
  • 78
  • 298
  • 470

2 Answers2

4

Create() returns a value of type interface{}, so type of x is interface{}, so type of &x is *interface{}, and not *SliceOfStrings. So x points to an interface{} value and not to a SliceOfStrings value!

If you type assert SliceOfStrings from the return value of Create(), it works:

x := Create().(SliceOfStrings)

Also add runtime.KeepAlive(x) at the end of your main(), because if you don't refer to x anymore, it can be garbage collected at any time.

With this change it works and outputs str1. Try it on the Go Playground.

In general, stay away from package unsafe as much as possible. You can learn and use pointers without package unsafe. Only think of unsafe as a last-last resort!

icza
  • 389,944
  • 63
  • 907
  • 827
  • That is so strange that when you do a type assertion you are changing the address of the variable. Comming from c++ it's hard to grasp. Anyways your answer was very helpful thank you. – Tono Nam Apr 15 '22 at 10:03
  • 2
    @TonoNam It's not type assertion that changes the address. There's nothing guaranteeing multiple runs will use the same memory addresses. The output on the Go Playground is cached, that's why you may see the same addresses printed (because the code may not be run again, and just the cached outputs are displayed). – icza Apr 15 '22 at 11:50
  • The address changes when doing type assertion. If you do `tmp := Create()` and then you do `x := tmp.(SliceOfStrings)` then tmp and x will have different addresses. That is what I meant. – Tono Nam Apr 15 '22 at 22:15
  • I understand now why. Its not that the address changes. I am copying a new variable and that is the reason why the address changes. Ok now I understand everything thanks icza – Tono Nam Apr 16 '22 at 00:02
1

I was able to understand why this happens:


package main

import (
    "fmt"
    "unsafe"
)

type SliceOfStrings []string

// when passing a slice to a method you are passing this data. Lets prove it
type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}



func main() {

    // On go everything is passed by coping values. When you pass a slice to a function you are passing this:
    // reference: https://stackoverflow.com/a/39993797/637142
    /*
        type SliceHeader struct {
            Data uintptr
            Len  int
            Cap  int
        }
    */

    // create a new slice
    var mySlice1 SliceOfStrings = make([]string, 0)

    // when appending we need to keep the new content that is why we reasig it
    mySlice1 = append(mySlice1, "str1")
    mySlice1 = append(mySlice1, "str2")
    // in other words this will make no sense:
    // _ = append(mySlice1, "str3") // doing this will lose the new header value

    // lets see the content of header mySlice
    var pointerToMySlice1 = unsafe.Pointer(&mySlice1)
    var header *SliceHeader = ((*SliceHeader)(pointerToMySlice1))
    fmt.Println(*header)
    // {824634220576 2 2}

    // lets copy that header to another slice
    var copy SliceOfStrings = mySlice1
    var pointerToCopy = unsafe.Pointer(&copy)
    header = ((*SliceHeader)(pointerToCopy))
    fmt.Println(*header)
    // prints the same thing
    // {824634220576 2 2}

    // now lets do the same thing but with an interface
    var copy2 interface{} = mySlice1
    var pointerToCopy2 = unsafe.Pointer(&copy2)
    header = ((*SliceHeader)(pointerToCopy2))
    fmt.Println(*header)
    // this prints!
    // {4845280 824634375976 0}
    // I dont understand why this happens. But this is the reason why the example does not work
    // I was trying to access an array from memory address 4845280 that is wrong

    // now lets do what izca told us
    var copy3 interface{} = mySlice1
    tmp := (copy3).(SliceOfStrings)
    var pointerToCopy3 = unsafe.Pointer(&tmp)
    header = ((*SliceHeader)(pointerToCopy3))
    fmt.Println(*header)
    // this prints!
    // {824634220576 2 2}
    // that is the correct value

    // lets try to get the array from that memory address (824634220576)
    pointerToActualArray := unsafe.Pointer(header.Data)
    // lets cast that to (*[2]string)
    var pointerFinal *[2]string = ((*[2]string)(pointerToActualArray))
    // now print the first value
    fmt.Println((*pointerFinal)[0]) 
    // prints str1
}



Tono Nam
  • 34,064
  • 78
  • 298
  • 470