Назад
Golang

Массивы и слайсы: что внутри?

В 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
    массив - да (если размер фиксирован)
    слайс - нет

Если коротко, то:

Массив - это сами данные, которые имеют ограничения по памяти и никогда не меняют ее размер
Слайс - это указатель на какие-то данные, которые могут перемещаться в другую ячейку памяти(при необходимости)

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