В чём разница между передачей массива и слайса в функцию в Go?

Ответ

Ключевое отличие в том, как Go обрабатывает передачу этих типов в функции. В Go всё передаётся по значению, но для массивов и слайсов это приводит к совершенно разному поведению.

Передача массива

При передаче массива в функцию создаётся полная копия всего массива. Любые изменения, сделанные внутри функции, затрагивают только эту копию и не влияют на исходный массив.

  • Последствия: Это может быть очень неэффективно с точки зрения производительности и потребления памяти, если массив большой.
func modifyArray(arr [3]int) {
    arr[0] = 100 // Изменяется только локальная копия
}

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

Передача слайса

Слайс — это легковесная структура-обёртка над массивом, которая состоит из трёх полей:

  1. Указатель на первый элемент базового массива.
  2. Длина (length) — количество элементов в слайсе.
  3. Ёмкость (capacity) — количество элементов от начала слайса до конца базового массива.

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

func modifySlice(slice []int) {
    // Изменяем элемент базового массива, на который указывает слайс
    slice[0] = 100
}

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

Важный нюанс с append

Если внутри функции вы используете append, и это приводит к превышению ёмкости (capacity) слайса, Go создаст новый базовый массив и скопирует туда старые элементы. Указатель в заголовке слайса внутри функции будет обновлён, но заголовок исходного слайса снаружи останется прежним.

func appendToSlice(slice []int) {
    slice = append(slice, 4) // Создаётся новый массив, т.к. capacity исчерпана
}

func main() {
    s := make([]int, 3, 3) // len=3, cap=3
    appendToSlice(s)
    fmt.Println(s) // Вывод: [0 0 0], а не [0 0 0 4]
}

Итог:

  • Массив: Передаётся по значению (копируется целиком). Дорого для больших массивов. Изменения внутри функции не видны снаружи.
  • Слайс: Передаётся по значению (копируется только заголовок). Эффективно. Изменения элементов внутри функции видны снаружи, так как оба заголовка (оригинал и копия) указывают на один и тот же базовый массив.