Слайс и массив в Go
Массив
Массив в Go — это последовательность элементов фиксированной длины одного типа. Длина массива является частью его типа.
var arr [5]int // массив из 5 int
arr2 := [3]string{"a", "b", "c"}
Ключевые характеристики:
- Фиксированный размер, известный на этапе компиляции
- Хранится в памяти как непрерывный блок
- Передаётся по значению (копируется при присваивании/передаче в функцию)
[5]intи[10]int— разные типы
Слайс
Слайс — это динамическая структура, представляющая собой "окно" в массив. Это абстракция над массивом с возможностью изменения размера.
var s []int // nil-слайс
s2 := make([]int, 5) // слайс длины 5
s3 := []int{1, 2, 3} // слайс из 3 элементов
Внутреннее устройство слайса (runtime.hslice):
type slice struct {
array unsafe.Pointer // указатель на первый элемент в underlying array
len int // длина слайса
cap int // ёмкость слайса
}
Основные отличия
| Характеристика | Массив | Слайс |
|---|---|---|
| Размер | Фиксированный, часть типа | Динамический |
| Передача | По значению (копия) | По ссылке (копия заголовка) |
| nil-значение | Не может быть nil | Может быть nil |
| Сравнение | Можно сравнивать == | Нельзя сравнивать (кроме nil) |
| Расширение | Невозможно | Через append |
Практические аспекты
1. Рост слайса через append
s := make([]int, 0, 2) // len=0, cap=2
s = append(s, 1) // len=1, cap=2
s = append(s, 2) // len=2, cap=2
s = append(s, 3) // len=3, cap=4 (удвоение)
При превышении ёмкости создаётся новый underlying array большего размера (обычно *2 для малых размеров, *1.25 для больших).
2. Срез слайса (slicing)
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // len=3, cap=4, указывает на arr[1]
Важно: срез не копирует данные, а создаёт новый заголовок, указывающий на тот же массив.
3. Проблема совместного использования underlying array
original := []int{1, 2, 3, 4, 5}
sub := original[1:3] // [2, 3]
sub[0] = 99 // original теперь [1, 99, 3, 4, 5]
4. nil vs empty slice
var s1 []int // nil, s1 == nil true
s2 := []int{} // empty, s2 == nil false
s3 := make([]int, 0) // empty, s3 == nil false
Оба ведут себя одинаково при итерации и len(), но nil-слайс не имеет underlying array.
Типичные ошибки
-
Путаница с передачей в функцию — слайс передаётся по значению (копия заголовка), но underlying array общий. Изменение элементов видно вызывающему, изменение длины/ёмкости — нет.
-
Утечка памяти — если держать ссылку на большой массив через маленький слайс, GC не освободит массив.
-
Неожиданное изменение данных — при использовании срезов разных слайсов от одного массива.
Рекомендации
- Используйте массивы только когда размер строго фиксирован и известен (например, для хэшей фиксированной длины)
- Для всех остальных случаев используйте слайсы
- При создании слайса с известным размером используйте
makeс указанием ёмкости для избежания лишних аллокаций - Для копирования данных используйте
copy()вместо присваивания срезов - При возврате под-слайса из функции, если нужно избежать утечки памяти, создавайте копию