Допустим, map хранит структуры: почему поле элемента нельзя изменить напрямую?
Элемент структуры, полученный индексированием map, не является адресуемым значением: чтение map[key] возвращает копию значения. Поэтому изменить его поле напрямую нельзя; нужно изменить копию и записать всю структуру обратно в map либо хранить в map указатели на структуры.
Map проектировалась как абстракция ассоциативного хранилища, а не как набор стабильных адресов элементов. Внутренняя организация map может изменяться при росте и перераспределении данных, поэтому Go не предоставляет адрес элемента через операцию индексирования.
Такое правило сохраняет простой контракт: чтение значения из map ведёт себя как получение значения, а не как получение ссылки на изменяемую ячейку.
Если значение map имеет структурный тип, выражение чтения возвращает отдельную копию структуры. Изменение поля этой копии не может автоматически изменить значение, сохранённое по ключу.
Неверное ожидание приводит либо к ошибке компиляции при прямом изменении поля, либо к незаметной потере изменения, если копию сначала сохранить в локальную переменную, но не записать её обратно.
Индекс map не является адресуемым выражением. Поэтому Go запрещает операции, которым требуется адрес изменяемого объекта, включая прямое присваивание полю и увеличение поля через ++.
Корректный паттерн для структурных значений — выполнить операцию чтения-модификации-записи:
Другой вариант — хранить указатели: тогда из map копируется указатель, а изменение объекта по этому указателю допустимо. Однако указатели усложняют управление временем жизни объектов и требуют учитывать возможное значение nil.
Важно отличать map от среза: элементы среза обычно адресуемы, поэтому поле структуры в срезе можно изменять напрямую по индексу. Это не меняет правила для map.
В кэше хранят счётчики запросов как значения структур. Разработчик увеличивает поле счётчика через элемент map и получает ошибку компиляции.
Можно использовать локальную копию с последующей записью обратно. Этот вариант хранит компактные значения и явно показывает обновление, но при конкурентном доступе операция чтения-модификации-записи всё равно должна быть защищена синхронизацией.
Можно заменить значение на указатель на счётчик. Тогда обновление поля проще, но появляются дополнительные выделения, косвенный доступ и необходимость корректно обрабатывать nil. Для небольших структур с редкими обновлениями выбран вариант с явной записью обратно; для часто изменяемых крупных объектов — указатели вместе с подходящей синхронизацией.
Нет, оператор адресации к результату индексирования map неприменим. Запрет связан не с конкретным типом значения, а с тем, что элемент map не является адресуемой переменной со стабильным расположением. Если нужен адресуемый объект, его следует создать отдельно или хранить в map указатель.
Срез сам является небольшой дескрипторной структурой, содержащей ссылку на массив, длину и вместимость. Чтение среза из map копирует этот дескриптор. Изменение существующего элемента через полученный срез может затронуть общий массив, но новый результат append, особенно при увеличении длины или выделении нового массива, нужно обязательно записать обратно в map, иначе обновлённая длина потеряется.
Присваивание по ключу — специальная операция записи в map, которой не требуется адрес элемента. Go вычисляет новое значение и помещает его по ключу. При изменении поля сначала потребовался бы адрес существующего элемента, а такой адрес map не предоставляет, поэтому разрешён только вариант с отдельной модификацией и последующей записью всей структуры.