Что происходит при передаче слайса в функцию? Как его правильно изменять?

Ответ

В Go слайс — это структура-заголовок (slice header), которая передается в функцию по значению.

Этот заголовок содержит три поля:

  1. Указатель на базовый массив (pointer to underlying array).
  2. Длина (length) — количество элементов в слайсе.
  3. Ёмкость (capacity) — количество элементов, которое может поместиться в базовом массиве, начиная с указателя.

Когда вы передаете слайс в функцию, создается копия этого заголовка. Оба заголовка (оригинальный и копия) изначально указывают на один и тот же базовый массив.

Это приводит к двум сценариям:

1. Изменение элементов слайса
Поскольку оба заголовка указывают на один массив, изменение элемента через копию слайса будет видно в оригинале.

func modifyElements(s []int) {
    s[0] = 100 // Это изменение отразится на оригинальном слайсе
}

func main() {
    originalSlice := []int{1, 2, 3}
    modifyElements(originalSlice)
    fmt.Println(originalSlice) // Вывод: [100 2 3]
}

2. Изменение самого слайса (например, через append)
Здесь поведение зависит от ёмкости.

  • Если append не превышает capacity: Новый элемент добавляется в тот же базовый массив. Длина изменяется только в локальной копии заголовка. Оригинальный слайс не изменится.
  • Если append превышает capacity: Go выделяет новый, больший массив, копирует в него старые элементы и добавляет новый. Локальный заголовок начинает указывать на этот новый массив. Оригинальный слайс остается нетронутым.
func addElement(s []int) {
    // Это изменение НЕ отразится на оригинальном слайсе,
    // так как s - это копия заголовка.
    s = append(s, 4)
    fmt.Println("Inside function:", s) // [1 2 3 4]
}

func main() {
    originalSlice := []int{1, 2, 3}
    addElement(originalSlice)
    fmt.Println("Outside function:", originalSlice) // [1 2 3]
}

Рекомендуемые подходы для изменения слайса


  1. Возвращать измененный слайс из функции (самый идиоматичный способ):


    func addElementCorrectly(s []int) []int {
    return append(s, 4)
    }

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


  2. *Передавать указатель на слайс (`[]int`):**

    Этот способ менее распространен, но может быть полезен, когда нужно изменить сам заголовок слайса (например, в методах).


    func addElementWithPointer(s *[]int) {
    *s = append(*s, 4)
    }

    func main() {
    s := []int{1, 2, 3}
    addElementWithPointer(&s)
    fmt.Println(s) // [1 2 3 4]
    }