Slice in Golang is a very flexible, extensible data structure. You may know that it is a reference type, meaning that when passed to a function or assigned to another variable, you can change the values inside it.
But you also should keep in mind that a slice contains the length and capacity property, the length stands for the actual number of elements inside the slice, and capacity denotes the max size when declared. While appending new elements into the slice, if not reach the capacity limit, then the elements are added into original slice buffer, otherwise a new buffer will be allocated with old elements copied and new elements appended, then the new slice is returned.
Also slice is a struct type containing a pointer to buffer under the hood, the metadata including length and capacity are passed by value. Example code is a better way of understanding this,
import "fmt"
func main() {
s1 := make([]int, 4)
s1[0] = 1
s1[1] = 2
s1[2] = 3
s2 := s1
s2[0] = 3
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
}
Output:
[3 2 3 0] 4 4
[3 2 3 0] 4 4
We append a new element to s2,
package main
import "fmt"
func main() {
s1 := make([]int, 4)
s1[0] = 1
s1[1] = 2
s1[2] = 3
s2 := s1
s2[0] = 3
s2 = append(s2, 5)
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
}
Output:
[3 2 3 0] 4 4
[3 2 3 0 5] 5 8
Now s2 is a new slice with no relationships with s1. What if s1 declared with a larger capacity?
package main
import "fmt"
func main() {
s1 := make([]int, 4, 8)
s1[0] = 1
s1[1] = 2
s1[2] = 3
s2 := s1
s2[0] = 3
s2 = append(s2, 5)
fmt.Println(s1, len(s1), cap(s1))
fmt.Println(s2, len(s2), cap(s2))
}
Output:
[3 2 3 0] 4 8
[3 2 3 0 5] 5 8
Although slice is a reference type, the underlying metadata is still passed by value, as in essence slice is implemented by struct, as follows,
type Slice struct {
ptr unsafe.Pointer // Array pointer
len int // slice length
cap int // slice capacity
}