← Назад к вопросам

Что копируется при передаче слайса в функцию?

2.2 Middle🔥 182 комментариев
#Основы Go

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Передача слайса в функцию в Go

При передаче слайса в функцию в Go копируется заголовок слайса (slice header), но не копируются сами элементы массива, на которые слайс ссылается. Это критически важное различие, которое определяет поведение слайсов в Go.

Структура заголовка слайса

Заголовок слайса — это структура, содержащая три поля:

  • Указатель на базовый массив
  • Длина (length) — количество элементов, доступных в слайсе
  • Ёмкость (capacity) — общий размер базового массива от начального элемента слайса
// Пример структуры заголовка слайса (не фактический код из runtime)
type sliceHeader struct {
    Pointer unsafe.Pointer // указатель на базовый массив
    Length  int           // текущая длина
    Capacity int          // максимальная ёмкость
}

Что происходит при передаче

func main() {
    original := []int{1, 2, 3, 4, 5}
    fmt.Printf("До: %v\n", original) // [1 2 3 4 5]
    
    modifySlice(original)
    fmt.Printf("После: %v\n", original) // [100 2 3 4 5]
}

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

В этом примере:

  1. При вызове modifySlice(original) копируется заголовок слайса
  2. Копия заголовка содержит тот же указатель на базовый массив
  3. Изменения элементов через индекс (s[0] = 100) меняют оригинальный массив
  4. Поэтому изменения видны в вызывающей функции

Важные нюансы поведения

Изменение длины и ёмкости

func appendToSlice(s []int) {
    s = append(s, 100)
    // Это НЕ изменит оригинальный слайс в main()
    // потому что изменяется копия заголовка
}

func main() {
    s := []int{1, 2, 3}
    appendToSlice(s)
    fmt.Println(s) // [1 2 3], а не [1 2 3 100]
}

Решение через возврат значения

func appendAndReturn(s []int) []int {
    return append(s, 100)
}

func main() {
    s := []int{1, 2, 3}
    s = appendAndReturn(s) // Теперь s = [1 2 3 100]
}

Передача по указателю

Если нужно изменить сам слайс (его длину/ёмкость) внутри функции:

func modifyWithPointer(s *[]int) {
    *s = append(*s, 100)
    (*s)[0] = 999
}

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

Сравнение с массивами

Для контраста, массивы копируются полностью:

func modifyArray(arr [3]int) {
    arr[0] = 100 // Изменяется только копия
}

func main() {
    arr := [3]int{1, 2, 3}
    modifyArray(arr)
    fmt.Println(arr) // [1 2 3] - без изменений
}

Практические рекомендации

  • Для чтения элементов передавайте слайс как есть — изменения элементов будут видны снаружи
  • Для изменения длины/ёмкости либо возвращайте новый слайс, либо передавайте указатель на слайс
  • Помните об аллокацияхappend может создать новый массив при превышении capacity
  • Используйте copy() для создания настоящих независимых копий:
func createCopy(s []int) []int {
    newSlice := make([]int, len(s))
    copy(newSlice, s)
    return newSlice
}

Вывод

Передача слайса в функцию — это копирование по значению заголовка слайса, а не копирование данных. Это делает слайсы эффективными для передачи (копируется всего 24 байта на 64-битной архитектуре), но требует понимания, что:

  1. Изменение элементов отражается на оригинале
  2. Изменение длины или ёмкости (через append) не отражается на оригинале, если не используется возврат значения или указатель
  3. Это "полу-ссылочный" тип данных, сочетающий эффективность передачи с необходимостью явного управления расширением