Можно ли ограничить capacity слайса в Go, чтобы append всегда создавал новый массив
Что проверяют
Знаете ли вы полное выражение среза s[low:high:max] и понимаете ли, почему два append к одному слайсу могут перетереть данные друг друга. Следом спрашивают, какой будет capacity после такого append и будет ли каждый следующий тоже создавать новый массив.
Как отвечать
Да, для этого есть трёхиндексный срез. Запись s[low:high:max] задаёт длину high-low и ёмкость max-low, поэтому s[:len(s):len(s)] даёт слайс с cap == len. Первый же append к нему не находит свободного места и выделяет новый массив, исходный остаётся нетронутым. С Go 1.21 то же самое делает slices.Clip(s).
Где это нужно:
- функция отдаёт часть своего буфера: без обрезки ёмкости вызывающий код сделает
appendи запишет поверх ваших элементов за границей длины; - несколько слайсов от одной базы:
a := append(base, 1)иb := append(base, 2)при свободной ёмкости пишут в одну ячейку, иa[len(base)]внезапно становится двойкой; - срез из большого массива: обрезка ёмкости память не освобождает, для этого нужна копия через
slices.Cloneилиappend([]T(nil), s...).
Важно проговорить границу гарантии. Новый массив создаётся только на первом append. Runtime выделяет его с запасом: небольшие слайсы растут примерно вдвое, большие медленнее. Значит, следующий append к результату снова пишет в тот же новый массив. Гарантия «всегда новый массив» держится, только если каждый раз добавлять к обрезанному исходнику.
Ограничения синтаксиса: должно выполняться low <= high <= max <= cap(s), иначе паника slice bounds out of range. Для строк трёхиндексный срез не разрешён.
Где спотыкаются
Думают, что s[:len(s):len(s)] копирует данные. Нет, до первого append это тот же массив, и запись по индексу видна в оригинале.
Обещают, что после обрезки копировать будет каждый append. На деле копирует только первый, а у полученного слайса снова есть запас ёмкости.
Пишут обёртку с проверкой len < cap и ручным копированием, не зная про встроенный синтаксис. Работает, но выдаёт незнание языка, а интервьюер ждал именно s[:len(s):len(s)].