Как передаются слайсы и мапы в Go: копируются ли они или передаются по указателю?
Что проверяют
Знаете ли вы, что в Go всё передаётся по значению, и понимаете ли, почему при этом изменения иногда видны снаружи, а иногда нет.
Как отвечать
Формально в Go всё передаётся по значению, включая слайсы и карты. Разница в том, что именно копируется.
Слайс это структура из трёх полей: указатель на массив, длина и ёмкость. Копируется эта структура, а массив остаётся общим. Отсюда двойственное поведение:
func setElem(s []int) { s[0] = 42 } // видно снаружи: массив общий
func addElem(s []int) { s = append(s, 1) } // не видно: изменилась копия заголовкаappend меняет длину в локальной копии, а при нехватке ёмкости вообще создаёт новый массив. Поэтому функции, изменяющие длину, возвращают новый слайс, как и сам append.
Карта это указатель на внутреннюю структуру. Копируется указатель, поэтому любые изменения видны снаружи, включая добавление и удаление ключей. Формально это тоже передача по значению, просто значение и есть указатель.
Так же устроены каналы: копируется указатель на внутреннюю структуру.
Что стоит добавить:
nil-карта: чтение из неё безопасно, запись вызывает панику. Карту нужно создавать черезmake;- общий массив у слайсов:
s[:2]разделяет память с исходным, и запись через один слайс видна через другой. Спасаютslices.Cloneи нарезка с ограничением ёмкости; - структуры копируются целиком, поэтому большие структуры передают указателем, а методы объявляют с указателем, если они меняют состояние.
Где спотыкаются
Говорят «слайсы и карты передаются по ссылке» и потом не могут объяснить, почему append не виден снаружи.
Мутируют срез, полученный из чужого слайса, и портят данные вызывающему.
Забывают инициализировать карту и получают панику при первой записи.