Some small details in Golang

Keywords: Go Back-end

1. The structures in golang can be compared directly. The bottom layer calls the memequal function to compare the memory. Memequal is implemented by assembly.

 

2. Only values are passed in golang. For example, when a slice is used as a formal parameter of a function, it is also a value transfer. The underlying data structure of the slice is:

// SliceHeader is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may
// change in a later release.
// Moreover, the Data field is not sufficient to guarantee the data
// it references will not be garbage collected, so programs must keep
// a separate, correctly typed pointer to the underlying data.
type SliceHeader struct {
	Data uintptr                           // Pointer to the underlying array
	Len  int                               // Array length
	Cap  int                               // Array capacity
}

Therefore, when passing parameters, it is equivalent to copying the fields in the SliceHeader structure of the argument to the formal parameter, and their Data points to the same piece of Data.

Let's start with a program:

package main

import (
	"fmt"
)

func main() {

	s := []int{10, 20, 30}
	fmt.Println(s)

	changeSlice(s)

	fmt.Println(s)
}

func changeSlice(s []int) {
    
	for i := 0; i < 10; i++ {
		s = append(s, i)
	}

	fmt.Println(s)
}

// Operation results
[10 20 30]
[10 20 30 0 1 2 3 4 5 6 7 8 9]
[10 20 30]

According to the above program, you can see that the slice is modified in changeSlice, but the one printed in the main function is not modified. We can guess the reason: because the Data is added to the changeSlice function, the space of the array is insufficient, so we apply for a new space, and then the Data of s in changeSlice points to the new space, but the s in the main function still points to the original space, so the s in the man function is still the original Data. As shown in the figure below:

You can use the program to verify the following:

package main

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

func main() {

	s := []int{10, 20, 30}
	fmt.Println("--------main--------------")
	fmt.Println(s)
	fmt.Println(unsafe.Pointer(&s))
	sptr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Println(unsafe.Pointer(sptr.Data))



	changeSlice(s)

	fmt.Println(s)

}

func changeSlice(s []int) {
	fmt.Println("--------changeSlice--------------")
	fmt.Println(s)
	fmt.Println(unsafe.Pointer(&s))
	sptr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Println(unsafe.Pointer(sptr.Data))

	for i := 0; i < 10; i++ {
		s = append(s, i)
	}

	fmt.Println(s)
	fmt.Println(unsafe.Pointer(&s))
	sptr1 := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Println(unsafe.Pointer(sptr1.Data))
}

// Operation results
--------main--------------
[10 20 30]
0xc000004078
0xc000014150
--------changeSlice--------------
[10 20 30]
0xc0000040a8
0xc000014150
[10 20 30 0 1 2 3 4 5 6 7 8 9]
0xc0000040a8
0xc000092000
[10 20 30]

As you can see, first, the addresses of the two s in the main function and the changeSlice function are different, but they point to the same address of the underlying data block. Moreover, after adding data to the changeSlice function, a new memory is requested.

 
But what if we set enough capacity in advance? In that way, new Data will not be opened up. Even after modification, the Data of two s still points to the same memory. Therefore, the modified Data printed in the main function should be modified. Is that really the case?

package main

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

func main() {

	s := make([]int, 3, 13)
	s[0] = 10
	s[1] = 20
	s[2] = 30

	fmt.Println("--------main--------------")
	fmt.Println(s)
	fmt.Println(unsafe.Pointer(&s))
	sptr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Println(unsafe.Pointer(sptr.Data))

	changeSlice(s)

	fmt.Println(s)

}

func changeSlice(s []int) {
	fmt.Println("--------changeSlice--------------")
	fmt.Println(s)
	fmt.Println(unsafe.Pointer(&s))
	sptr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Println(unsafe.Pointer(sptr.Data))

	for i := 0; i < 10; i++ {
		s = append(s, i)
	}

	fmt.Println(s)
	fmt.Println(unsafe.Pointer(&s))
	sptr1 := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Println(unsafe.Pointer(sptr1.Data))
}

// Operation results
--------main--------------
[10 20 30]
0xc000004078
0xc00001a0e0
--------changeSlice--------------
[10 20 30]
0xc0000040a8
0xc00001a0e0
[10 20 30 0 1 2 3 4 5 6 7 8 9]
0xc0000040a8
0xc00001a0e0
[10 20 30]

In the above code, the capacity of 13 was specified when creating the slice, but the result was unexpected. It can be seen that the original data is still printed. Moreover, after adding data to the changeSlice function, the memory address pointed to has not changed, so why is there only three elements printed?

It can be guessed again that although the underlying data is modified, only Len and Cap in s are modified in changeSlice, while Len and Cap in s in main function are not changed. Therefore, although the data is added successfully, Len and Cap of s in main function are still 3 and 13, so only three elements are printed. As shown below:

Also use code to verify:

package main

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

func main() {

	s := make([]int, 3, 13)
	s[0] = 10
	s[1] = 20
	s[2] = 30

	fmt.Println("--------main--------------")
	fmt.Println(s)
	fmt.Println(unsafe.Pointer(&s))
	sptr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Println(unsafe.Pointer(sptr.Data))

	changeSlice(s)

	fmt.Println("--------main--------------")
	fmt.Println(s)
	fmt.Println(sptr.Len)
	fmt.Println(sptr.Cap)

	sptr.Len = 13
	fmt.Println(s)
}

func changeSlice(s []int) {
	fmt.Println("--------changeSlice--------------")
	fmt.Println(s)
	fmt.Println(unsafe.Pointer(&s))
	sptr := (*reflect.SliceHeader)(unsafe.Pointer(&s))
	fmt.Println(unsafe.Pointer(sptr.Data))

	for i := 0; i < 10; i++ {
		s = append(s, i)
	}

	fmt.Println(s)
	fmt.Println(unsafe.Pointer(&s))
	fmt.Println(unsafe.Pointer(sptr.Data))

	fmt.Println(sptr.Len)
	fmt.Println(sptr.Cap)
}

// Operation results
--------main--------------
[10 20 30]
0xc000004078
0xc00001a0e0
--------changeSlice--------------
[10 20 30]
0xc0000040a8
0xc00001a0e0
[10 20 30 0 1 2 3 4 5 6 7 8 9]
0xc0000040a8
0xc00001a0e0
13
13
--------main--------------
[10 20 30]
3
13
[10 20 30 0 1 2 3 4 5 6 7 8 9]

It can be seen that after calling changeSlice, the Len of s in the main function is still 3, but the underlying data has been changed. Therefore, manually changing the Len of s in the main function to 13 also successfully prints the expected results. This is why the go built-in append function returns a slice after appending data.

Posted by vivekjain on Mon, 08 Nov 2021 23:47:01 -0800