append и мутируемость слайса

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
}

Стайл гайд рекомендует второй способ, с возвратом обновлённого значения.



Report Page