Можно ли взять указатель на элемент в map Go и почему
Что проверяют
Понимаете ли вы адресуемость в Go и почему map устроена так, что её элемент нельзя «закрепить» указателем. Следом обычно спрашивают, как тогда поменять поле структуры, которая лежит в map.
Как отвечать
Нельзя: p := &m["a"] не компилируется, компилятор отвечает cannot take address of m["a"]. Элемент map неадресуем по спецификации языка.
Причина в устройстве map. При росте таблица перестраивается и переносит элементы в новое хранилище. Так делала старая реализация с бакетами, так делают и Swiss tables, на которые map перешла в Go 1.24. Указатель на старое место смотрел бы в устаревшую копию, и запрет в компиляторе убирает этот класс ошибок целиком.
Следствия, которые стоит назвать:
- присваивание полю:
m["a"].Count++дляmap[string]Statsтоже не соберётся, ошибкаcannot assign to struct field ... in map; - методы по указателю:
m["a"].Inc()не скомпилируется, еслиIncобъявлен на*Stats: взять адрес нельзя; - range: в
for k, v := range mпеременнаяvэто копия, и&vна элемент map не указывает.
Обходные пути:
- копировать и записать обратно:
s := m["a"]; s.Count++; m["a"] = s; - хранить указатели:
map[string]*Stats, тогдаm["a"].Count++работает, меняется объект в куче, а в map лежит только адрес; - индекс в слайс:
map[string]intхранит позицию в[]Stats, если нужны и быстрый поиск, и плотное хранение.
С указателями помните про конкурентность: map не потокобезопасна (одновременная запись даёт fatal error: concurrent map writes), а объект по указателю защищают отдельно.
Где спотыкаются
Путают указатель на элемент с указателем, который хранится в элементе. p := m["a"] при map[string]*T просто копирует адрес, это не адрес ячейки map.
Говорят, что после delete указатель из map становится невалидным. Со сборщиком мусора объект живёт, пока на него есть ссылка, delete убирает только запись.
Объясняют запрет тем, что «map это ссылка». Дело не в этом: элементы физически перемещаются при росте таблицы.