Какие ключевые особенности типа slice в Go
Что проверяют
Понимаете ли вы, что слайс это маленькая структура поверх массива, и можете ли предсказать, когда два слайса делят память. Следом обычно дают задачку с append внутри функции и спрашивают, что напечатается.
Как отвечать
- устройство: заголовок из трёх полей, указатель на базовый массив,
lenиcap. При передаче в функцию копируется только заголовок, поэтому изменения элементов видны снаружи, а изменение длины нет; - нарезка:
s[low:high]создаёт новый заголовок над тем же массивом, без копирования. Полная формаs[low:high:max]ограничиваетcap, и следующийappendбудет вынужден выделить новый массив; append: пока хватаетcap, пишет в существующий массив, в том числе поверх данных, которые видит другой слайс. Если места нет, выделяет новый массив и копирует элементы. Маленькие слайсы растут примерно вдвое, после порога в 256 элементов коэффициент плавно снижается к 1,25, а итог округляется до классов размеров аллокатора. Поэтому результат всегда присваивают:s = append(s, x);nilи пустой:var s []intравенnil,[]int{}нет. Оба имеют длину 0 и одинаково работают сlen,rangeиappend, но в JSON первый станетnull, а второй[];make([]T, len, cap): заранее выделяет ёмкость, когда размер известен, и избавляет от перевыделений;- сравнение: через
==слайс сравнивается только сnil. Для содержимого естьslices.Equalиз пакетаslices, он в стандартной библиотеке с Go 1.21, там жеClone,Contains,Sort.
Независимую копию делают через copy(dst, src) или slices.Clone.
Где спотыкаются
Думают, что append внутри функции изменит слайс вызывающего. Длина снаружи не поменяется, а при достаточной ёмкости вызов ещё и перезапишет элементы базового массива.
Держат маленький подслайс от большого буфера, например первые 10 байт от прочитанного файла на 100 МБ. Сборщик мусора не освободит массив, пока жив подслайс, поэтому нужные данные копируют.
Меняют слайс внутри for range и ждут, что цикл это учтёт. Выражение диапазона вычисляется один раз, добавленные в цикле элементы обход не увидит.