Массивы и слайсы: что внутри?
В Go массив и слайс - это принципиально разные типы данных с совершенно разным представлением в памяти.
Массив [N]T
- Это непрерывный блок памяти размером ровно N × sizeof(T)
- Размер N - неотъемлемая часть типа (
[16]byte, [32]byte и тд) - При присваивании, возврате из функции или передаче по значению = полное побайтовое копирование всего блока
- Нулевое значение - массив, все элементы которого имеют нулевое значение типа T
- Никогда не может быть nil
- Размер известен на этапе компиляции и неизменяем
Из-за этого массивы:
- очень дорогие при копировании (особенно большие)
- почти никогда не передаются по значению в реальном коде
- используются только там, где фиксированный размер важен для типа (ключи в map, [4]uint8, [16]byte для SHA-256 и т.п.)
Слайс []T
Слайс - это структура-дескриптор размером 24 байта (на 64-битных системах), состоящая из трёх полей:
- указатель на базовый массив (
*T) - 8 байт - текущая длина (
len) - 8 байт - ёмкость (
cap) - 8 байт
Когда вы создаёте слайс, в памяти происходит примерно следующее:
переменная s (24 байта)
├── указатель ──► [начало данных в куче]
├── len = текущая видимая длина
└── cap = доступная ёмкость от указателя до конца выделенного блока
Основные технические свойства слайсов:
- Присваивание, передача в функцию, возврат из функции - копируется только 24 байта дескриптора
- Сам массив - основа не копируется при таких операциях
- Несколько слайсов могут ссылаться на одну и ту же область памяти (разные указатели, len и cap)
- Может быть nil (все три поля = 0 / nil)
- Может иметь len == 0, но уже подготовленную cap (пустой слайс с зарезервированной памятью)
- Может указывать на произвольную середину другого массива или слайса
- При append, если cap недостаточно - происходит аллокация: новый больший блок - копирование элементов - обновление указателя, len и cap в дескрипторе
- Сравнение через == запрещено компилятором (кроме сравнения с
nil)
Основные технические различия
-
Размер в памяти самой переменной
массив - N × sizeof(T)
слайс - всегда 24 байта -
Что копируется при присваивании / передаче в функцию
массив - весь массив
слайс - только дескриптор (указатель + len + cap) -
Возможность быть nil
массив - нет
слайс - да -
Возможность иметь len == 0 при cap > 0
массив - нет
слайс - да -
Возможность указывать на часть другого массива
массив - нет
слайс - да -
Изменение данных через один слайс видно в другом (если общая область памяти)
массив - нет
слайс - да -
Поддержка динамического роста (
append)
массив - нет
слайс - да (с возможной аллокацией) -
Можно ли использовать как ключ в map
массив - да (если размер фиксирован)
слайс - нет
Если коротко, то:
Массив - это сами данные, которые имеют ограничения по памяти и никогда не меняют ее размер
Слайс - это указатель на какие-то данные, которые могут перемещаться в другую ячейку памяти(при необходимости)
Поэтому в коде редко используются именно массивы, они неудобны и намного «дороже» слайсов. Используйте их только при необходимости, в остальных случаях выбирайте слайс