append и мутируемость слайса
package main
import (
"fmt"
"log"
)
func main() {
arr := make([]int, 3, 3)
fmt.Println(arr, len(arr), cap(arr))
log.Printf("%p", arr)
addElems(arr)
fmt.Println(arr, len(arr), cap(arr))
log.Printf("%p", arr)
}
func addElems(arr []int) {
log.Printf("%p", arr)
arr = append(arr, 4)
arr = append(arr, 5)
arr = append(arr, 6)
fmt.Println(arr, len(arr), cap(arr))
log.Printf("%p", arr)
}
Слайс мутирует внутри функции, так устроен гоу, но если делать append, то всё работает иначе. Я думаю, что это потому, что append возвращает новый адрес и вставляет в переменную arr, но не понятно, почему сама arr не мутировала.
Я решил сделать другой пример
package main
import (
"fmt"
"log"
)
func main() {
arr := make([]int, 3, 8)
fmt.Println(arr, len(arr), cap(arr))
log.Printf("%p", arr)
addElems(arr)
fmt.Println(arr, len(arr), cap(arr))
log.Printf("%p", arr)
}
func addElems(arr []int) {
log.Printf("%p", arr)
arr = append(arr, 4)
arr = append(arr, 5)
arr = append(arr, 6)
fmt.Println(arr, len(arr), cap(arr))
log.Printf("%p", arr)
}
Тут append ни разу новый адрес не вернул, почему тогда на выходе из функции arr один, а в после работы функции в меине он уже другой?
Срез - это не указатель. Это структура данных из трёх полей, которая в функцию передаётся по значению.
Вот, смотрите:
package main
import (
"fmt"
)
func main() {
arr := make([]int, 3, 3)
fmt.Println("main: ", arr, len(arr), cap(arr))
fmt.Printf("main: %p->%p\n", &arr, arr)
addElems(arr)
fmt.Println("main: ", arr, len(arr), cap(arr))
fmt.Printf("main: %p->%p\n", &arr, arr)
}
func addElems(arr []int) {
fmt.Printf(" addElems: %p->%p\n", &arr, arr)
arr = append(arr, 4)
arr = append(arr, 5)
arr = append(arr, 6)
fmt.Println(" ", arr, len(arr), cap(arr))
fmt.Printf(" addElems: %p->%p\n", &arr, arr)
}
main: [0 0 0] 3 3
main: 0xc000010018->0xc00001a018
addElems: 0xc000010060->0xc00001a018
[0 0 0 4 5 6] 6 6
addElems: 0xc000010060->0xc000108000
main: [0 0 0] 3 3
main: 0xc000010018->0xc00001a018
В main переменная arr находилась по адресу 0xc000010018, указатель на данные был 0xc00001a018
Но в функции addElems все операции происходят с совсем другим объектом, лежащим в памяти по адресу 0xc000010060. То, что в этом объекте изменился указатель, никак не отображается на первом объекте. Аналогично с len и cap - они изменились в значении, которое лежит на стеке addElems, но эти изменения никак не отразились на исходный объект.
В случае make([]int, 3, 8) указатель не изменился, так как не потребовалась переаллокация. Но и в этом случае исходный слайс ничего не узнал об изменениях в копии, поэтому len и cap не изменились, несмотря на добавление трёх элементов.
Если вам нужно, чтобы обновился исходный объект, то нужно либо передавать указатель
func addElems(arr *[]int) {
fmt.Printf(" addElems: %p->%p\n", arr, *arr)
*arr = append(*arr, 4)
*arr = append(*arr, 5)
*arr = append(*arr, 6)
fmt.Println(" ", *arr, len(*arr), cap(*arr))
fmt.Printf(" addElems: %p->%p\n", arr, *arr)
}
либо возвращать изменённый объект
func addElems(arr []int) []int {
fmt.Printf(" addElems: %p->%p\n", &arr, arr)
arr = append(arr, 4)
arr = append(arr, 5)
arr = append(arr, 6)
fmt.Println(" ", arr, len(arr), cap(arr))
fmt.Printf(" addElems: %p->%p\n", &arr, arr)
return arr
}
Стайл гайд рекомендует второй способ, с возвратом обновлённого значения.