Home > Software engineering >  Copying struct value to a interface{ } in golang
Copying struct value to a interface{ } in golang

Time:04-16

I will like to understand why when I copy a struct value into an interface it behaves like it does. In this code can someone help me understand why when I copy the value from mySlice into foo3 it behaves different than the other copies?

package main

import (
    "fmt"
    "unsafe"
)

type SliceOfInt []int

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

// Print the header of a slice. Its Data. Len and Cap
func GetHeaderOfSliceOfInt(s unsafe.Pointer) *SliceHeader {
    // this header needs to point to &mySlice but compiler will not let us. we have to use unsafe pointers
    var header *SliceHeader
    pointerToMySlice := s
    header = ((*SliceHeader)(pointerToMySlice))
    return header
}

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 mySlice SliceOfInt = make([]int, 0)
    mySlice = append(mySlice, 123456789) // append this number to mySlice

    // now we have a slice with len:1 and capacity:1. Lets prove it

    header := GetHeaderOfSliceOfInt(unsafe.Pointer(&mySlice))
    fmt.Println(*header)
    // this prints: {824635465728 1 1}
    // this means that on memory address 824635465728 there is an array with cap:1 and len:1

    // copy that header to someOtherSlice
    someOtherSlice := mySlice
    header = GetHeaderOfSliceOfInt(unsafe.Pointer(&someOtherSlice))
    fmt.Println(*header)
    // prints the same value {824635465728 1 1}

    // anyways if I go to address 824635465728 on my computer I shoul dbe able to find integer 123456789
    pointerToInteger := unsafe.Pointer((*header).Data)
    var integerVal *int = ((*int)(pointerToInteger))
    fmt.Println(*integerVal)

    // if I copy like this, it will print the correct header {824635465728 1 1}
    foo1 := mySlice
    fmt.Println(*GetHeaderOfSliceOfInt(unsafe.Pointer(&foo1)))

    // copy like this will also print the correct header {824635465728 1 1}
    foo2 := foo1
    fmt.Println(*GetHeaderOfSliceOfInt(unsafe.Pointer(&foo2)))

    // If I copy like this it will print the incorrect header. Why?
    var foo3 interface{} = mySlice
    fmt.Println(*GetHeaderOfSliceOfInt(unsafe.Pointer(&foo3)))

    // this last line prints {4746976 824635330392 0}
}

The output of the program is:

{824635465728 1 1}
{824635465728 1 1}
123456789
{824635465728 1 1}
{824635465728 1 1}
{4746976 824635330392 0}

Edit

I know that if I cast foo3 as: foo3.(SliceOfInt) it will work. But why is that?

CodePudding user response:

An interface type, empty or not, is a type in its own right. It has its own memory representation and it is a legitimate member of Go's type system.

An interface value, and the value wrapped in that interface, are not one and the same.

The variables foo1 and foo2 have the same type and value as the mySlice variable. But the variable foo3 has a different type, therefore also a different value. And yes, the dynamic type and value are the same as mySlice but the static type and value are not.

An interface value is NOT represented in memory by a structure that's compatible with the three-field SliceHeader and therefore it is wrong, not only semantically, to try to take the slice header of of an interface value. Instead, an interface value is represented by a 2-field structure (that's why in your attempt the third field is 0). The first field points to the type information of the wrapped value, the second field points to the data of the wrapped value.

Something like this:

type iface struct {
    typ  uintptr
    data uintptr
}

And you can test this by doing this:

x := (*iface)(unsafe.Pointer(&foo3))
s := (*SliceHeader)(unsafe.Pointer(x.data))
fmt.Printf("% v\n", x)
fmt.Printf("% v\n", s)

https://go.dev/play/p/2KUgCU8h7O7


Also, consider reading this: https://research.swtch.com/interfaces.

  • Related