ПрограммированиеGo программист

Что такое встроенные функции append, len и cap в Go, как они работают на уровне языка, и какие скрытые опасности срезов связаны с их использованием?

Проходите собеседования с ИИ помощником Hintsage

Ответ

В Go встроенные функции append, len и cap играют ключевую роль при работе со срезами (slices).

  • len(slice) возвращает количество элементов в срезе.
  • cap(slice) возвращает вместимость среза — максимальное количество элементов до выделения новой памяти.
  • append(slice, elems ...T) при недостаточной ёмкости приводит к созданию нового массива (и, соответственно, нового подлежащего хранилища), что может неожиданно изменить семантику передачи среза.

Пример:

arr := []int{1,2,3} arr2 := append(arr, 4) // arr2 может быть на том же или на новом backing array, в зависимости от cap(arr)

Тонкости:

  • Если вы делаете append к срезу, который был "вырезан" из другого среза (или исходного массива), возможен side-effect: изменение исходных данных.
  • При передаче срезов в функции len/cap действуют только на "видимую" часть.
  • При append с превышением cap создаётся новый массив, старый не трогается.

Вопрос с подвохом

Что произойдёт, если срезу "отрезать" кусок и добавить элемент через append? Повлияет ли это на исходный массив?

Ответ: Если cap позволяет, append запишет элемент "в хвост" исходного массива, и изменения видны через все срезы, которые ссылаются на тот же массив.

Пример:

a := []int{1,2,3,4} b := a[:2] // [1 2], len=2, cap=4 b = append(b, 10) // меняется a: a -> [1, 2, 10, 4]

Примеры реальных ошибок


История

В команде добавили элементы в дочерний срез, и неожиданно изменились данные в родительском массиве — это вызвало рассогласование бизнес-логики и сложнейшие для дебага баги в распределении задач между пользователями.


История

При повторном вызове append на большом срезе ожидали, что новый массив всегда будет выделяться заново, но по факту несколько частей системы продолжали работать с одним и тем же "backing array", провоцируя race condition и повреждение данных.


История

Разработчик выделял срез фиксированного размера с помощью make, но допустил ошибку и поменял местами аргументы: make([]int, cap, len). В результате логика, ориентированная на вместимость, неожиданно работала на длину, что спровоцировало панику при выходе за пределы s[0:len].