Срезы и массивы — одни из самых используемых структур данных в Go. Несмотря на сходный синтаксис, разница в их устройстве и поведении может приводить к ошибкам производительности, памяти и семантики.
История вопроса:
Go с самого начала выбрал явную модель управления памятью, в которой массивы (arrays) — это последовательность элементов с фиксированным размером, а срезы (slices) — динамический вид на массив. Такое разделение позволяет контролировать стоимость операций и поведение кода.
Проблема:
Основная сложность — путаница между копированием массива (value semantics) и "ссылочностью" среза. Ошибки часто возникают при передаче этих типов в функции и изменении значений, приводя к неожиданным побочным эффектам.
Решение:
Массивы всегда копируются при передаче по значению: функция получает копию всего содержимого. Срез же — маленькая структура (header), которая содержит указатель на массив, длину и вместимость. Изменения внутри среза видимы снаружи, если изменяется содержимое массива (но не если сам срез перенаправлен на новый массив внутри функции).
Пример кода:
func updateArray(arr [3]int) { arr[0] = 10 } func updateSlice(slc []int) { slc[0] = 10 } func main() { a := [3]int{1,2,3} b := []int{1,2,3} updateArray(a) updateSlice(b) fmt.Println(a) // [1 2 3] fmt.Println(b) // [10 2 3] }
Ключевые особенности:
Что произойдет, если изменить длину среза внутри функции? Повлияет ли это на исходный срез?
Нет, изменение длины среза (например, с помощью slc = slc[:2]) внутри функции повлияет только на локальную копию header. Исходный срез останется прежним.
Возвращает ли оператор append изменённый срез в той же области памяти?
Не обязательно. Если вместимости недостаточно, создаётся новый массив, а указатель на новый массив возвращается. Старый массив останется нетронутым.
Пример кода:
s := []int{1,2,3} s2 := append(s, 4, 5, 6) // s2 может быть в новой области памяти
Можно ли присвоить срезу массив или наоборот?
Нет. []int и [5]int — разные типы. Для передачи массива как среза нужно воспользоваться преобразованием arr[:]. Обратное невозможно.
Младший разработчик реализовал функцию обновления таблицы, передавая массив в функцию с ожиданием, что изменения будут применяться к исходному массиву. Правки не "сохранялись".
Плюсы:
Минусы:
Функция принимала срез, и явно возвращала изменённую копию, повышая предсказуемость эффекта. Все изменения были осознанными, данные не "утекали" и не изменялись неявно.
Плюсы:
Минусы: