Объясните механизм адресуемости в Go: почему можно взять адрес переменной и элемента среза, но нельзя — результата вызова функции или элемента map?
Адресуемым является выражение, однозначно обозначающее существующее место хранения, адрес которого может быть использован. Переменная и элемент среза обычно соответствуют такому месту, а результат вызова функции и элемент map считаются временным или внутренне перемещаемым значением, поэтому взять их адрес напрямую нельзя.
Адресуемость — часть базовой модели памяти Go, которая отделяет стабильные объекты хранения от значений, существующих только как результат вычисления. Такой подход позволяет компилятору свободнее размещать временные значения и не обещать вызывающему коду адрес внутреннего результата.
Попытка взять адрес неадресуемого выражения приводит к ошибке компиляции. Особенно часто это проявляется с результатами функций, элементами map и полями структур, хранящихся внутри map.
Нельзя считать, что наличие конкретного типа автоматически означает наличие доступного адреса. Адресуемость определяется не типом, а формой выражения и способом получения значения.
Локальная или глобальная переменная адресуема, потому что обозначает конкретную область хранения. Элемент среза также адресуем: срез обращается к элементу своего backing-массива, пока операция обращения выполняется через индекс среза.
Результат вызова функции — это значение, полученное вычислением, а не имя существующего объекта, к которому язык разрешает привязать указатель. Поэтому выражение вроде &makeValue() недопустимо.
Элемент map неадресуем. Реализация может перемещать элементы внутри таблицы при росте map, поэтому указатель на внутреннюю ячейку нарушил бы гарантии языка. Чтение разрешено, но изменение поля структуры в элементе map напрямую запрещено: нужно извлечь структуру, изменить копию и записать её обратно.
Есть специальные случаи. Составной литерал можно адресовать непосредственно, например &Point{}: компилятор создаёт для него временное значение с допустимым временем жизни. Индекс элемента массива адресуем, если сам массив адресуем; индекс строки неадресуем, потому что строка неизменяема.
Минимальный пример:
points[0] адресуем, поэтому его поле можно изменить через указатель. values["a"] сначала копируется из map, поэтому изменение v не меняет map до обратной записи.
В обработчике данных разработчик попытался изменить поле записи непосредственно внутри map и получил ошибку компиляции. Рассматривались два варианта: заменить map на map указателей либо извлекать структуру, изменять её и записывать обратно.
Map указателей упрощает изменение полей, но усложняет владение объектами, допускает nil-указатели и требует дополнительного контроля времени жизни. Извлечение и обратная запись не меняют модель данных, но копируют структуру и могут быть неудобны для крупных значений.
Если записи небольшие и не требуется разделяемая изменяемость, выбран второй вариант. Он сохраняет понятную семантику значения и явно показывает момент обновления map. Для крупных объектов или объектов с самостоятельным жизненным циклом обоснованным выбором может быть map указателей.
Вопрос: Можно ли изменить элемент среза через указатель, полученный из его индекса?
Ответ: Да, если индекс находится в допустимом диапазоне. Элемент среза адресуем, поэтому указатель на него указывает на элемент backing-массива. Однако после операции append, которая выделила новый массив, ранее полученный указатель продолжит указывать на старый массив и не будет связан с новым содержимым среза.
Вопрос: Почему поле структуры из map нельзя изменить напрямую, даже если сама структура содержит указатель?
Ответ: Неадресуемым является весь элемент map, поэтому нельзя обратиться к его полю как к изменяемому месту хранения. Но если поле уже имеет указательный тип, можно извлечь элемент и изменить объект, на который указывает это поле: изменится общий объект, а не сама структура, хранящаяся в map.
Вопрос: Почему составной литерал можно адресовать, хотя он не записан в отдельную переменную?
Ответ: Go разрешает адрес составного литерала как специальный случай. Компилятор создаёт для него значение с необходимым временем жизни, поэтому указатель остаётся корректным после завершения текущего выражения. Это не означает, что адрес можно брать у любого временного результата: разрешение действует именно для предусмотренных языком составных литералов.