Как слайсы передаются в функции в Go? Что произойдет, если использовать `append` для слайса внутри функции?

Ответ

Слайсы в Go передаются в функции по значению.

Это означает, что копируется сама структура слайса (так называемый slice header), которая содержит:

  1. Указатель на базовый массив (underlying array).
  2. Длину (len).
  3. Ёмкость (cap).

Из-за этого поведение при изменении слайса внутри функции может быть неочевидным.

Сценарий 1: Изменение существующих элементов
Так как и оригинальный слайс, и его копия в функции указывают на один и тот же базовый массив, изменение элементов через индекс будет видно снаружи.

Сценарий 2: Использование append
Здесь поведение зависит от ёмкости (cap) слайса:

  • Если cap достаточна: append добавляет элемент в тот же базовый массив. Длина (len) в копии слайса внутри функции увеличивается, но len оригинального слайса не меняется. Изменения не будут видны снаружи.
  • Если cap недостаточна: append создает новый, больший массив, копирует в него старые элементы и добавляет новый. Указатель в копии слайса теперь указывает на этот новый массив. Оригинальный слайс и его базовый массив остаются нетронутыми.

Пример проблемы:

func modifySlice(s []int) {
    s[0] = 100       // Это изменение будет видно снаружи
    s = append(s, 4) // Это изменение НЕ будет видно снаружи
    fmt.Printf("Внутри функции: len=%d, cap=%d, data=%vn", len(s), cap(s), s)
}

func main() {
    s := make([]int, 3, 3) // len=3, cap=3
    s[0], s[1], s[2] = 1, 2, 3

    fmt.Printf("Снаружи до: len=%d, cap=%d, data=%vn", len(s), cap(s), s)
    modifySlice(s)
    fmt.Printf("Снаружи после: len=%d, cap=%d, data=%vn", len(s), cap(s), s)
}
// Вывод:
// Снаружи до: len=3, cap=3, data=[1 2 3]
// Внутри функции: len=4, cap=6, data=[100 2 3 4] (создан новый массив)
// Снаружи после: len=3, cap=3, data=[100 2 3] 

Правильный (идиоматичный) подход

Чтобы надежно изменять слайс, функция должна возвращать его обновленную версию.

func addElement(s []int, value int) []int {
    return append(s, value)
}

func main() {
    s := []int{1, 2, 3}
    s = addElement(s, 4) // Переприсваиваем результат
    fmt.Println(s) // Вывод: [1 2 3 4]
}

Альтернативный, но менее распространенный способ — передавать в функцию указатель на слайс (*[]int).