Из чего состоит слайс (slice) в Go и как он устроен?

Ответ

Слайс в Go — это не массив, а легковесная структура-обертка (дескриптор) над существующим массивом. Внутренне он представляет собой структуру, состоящую из трех полей:

  1. Указатель (ptr): Адрес первого элемента базового массива, к которому имеет доступ слайс.
  2. Длина (len): Текущее количество элементов в слайсе. Это то, что возвращает функция len().
  3. Ёмкость (cap): Максимальное количество элементов, которое может содержать слайс, начиная от своего первого элемента, без необходимости переаллокации памяти. Это то, что возвращает функция cap().
// Создаем слайс с длиной 3 и ёмкостью 5
// Go выделяет в памяти массив на 5 элементов
// и создает слайс, который на него указывает.
s := make([]int, 3, 5) 

fmt.Printf("len=%d, cap=%d, slice=%vn", len(s), cap(s), s)
// Вывод: len=3, cap=5, slice=[0 0 0]

Ключевая особенность: связь с базовым массивом

Поскольку слайс — это всего лишь обертка, несколько слайсов могут указывать на один и тот же базовый массив. Изменение элемента в одном слайсе будет видно в другом, если они разделяют этот элемент.

// Базовый массив
underlyingArray := [5]int{10, 20, 30, 40, 50}

// Два слайса, указывающие на один массив
s1 := underlyingArray[0:3] // {10, 20, 30}, len=3, cap=5
s2 := underlyingArray[2:5] // {30, 40, 50}, len=3, cap=3

fmt.Println("Before:", s1, s2) // Before: [10 20 30] [30 40 50]

// Изменяем элемент через первый слайс
s1[2] = 35

// Изменение видно и в базовом массиве, и во втором слайсе
fmt.Println("After: ", s1, s2) // After:  [10 20 35] [35 40 50]
fmt.Println("Array: ", underlyingArray) // Array:  [10 20 35 40 50]

Когда вы используете append и ёмкость слайса превышается, Go создает новый, больший массив, копирует в него все элементы из старого и возвращает слайс, указывающий уже на этот новый массив. Связь со старым массивом при этом теряется.