Почему программа падает при работе с указателем на структуру и как это связано с арифметикой указателей в Go
Что проверяют
Умеете ли вы объяснить панику на nil-указателе и понимаете ли, почему арифметики указателей в Go нет намеренно. Вопрос часто задают тем, кто пришёл из C или C++, чтобы услышать разницу в модели памяти.
Как отвечать
Типовая причина падения одна: разыменование нулевого указателя. var p *T равен nil, и обращение p.Field даёт panic: runtime error: invalid memory address or nil pointer dereference. Это именно паника, а не сегфолт: её видно в трейсе и можно перехватить через recover.
Важная деталь, которую ждут: метод на nil-приёмнике вызвать можно, если он не трогает поля. Падает не вызов, а первое обращение к полю внутри тела.
Арифметики указателей в языке нет, чтобы сборщик мусора всегда знал, на какой объект указывает каждое слово памяти. Нельзя ни p++, ни сложение с числом. Обход существует через unsafe.Pointer вместе с unsafe.Add и unsafe.Slice, и вот там программа падает по-настоящему: как только адрес превращён в uintptr, это уже обычное число, объект перестаёт считаться живым, а стек горутины при росте переезжает, и сохранённый адрес указывает в чужую память.
Стоит назвать и третий источник: указатель на элемент слайса, сохранённый до append. После роста массива он ведёт в старую копию, и правки уходят в никуда.
Где спотыкаются
Путают nil-указатель и nil-интерфейс. Указатель типа *MyErr со значением nil, положенный в переменную error, делает сравнение с nil ложным, и код уходит в ветку ошибки при отсутствии ошибки. Это самая частая ловушка по теме.
Считают, что nil-указатель означает падение при любом вызове. Метод отработал, значит объект есть, рассуждает кандидат, и пропускает проверку там, где поля читаются позже.
Нарушают правила преобразования в unsafe. Конверсия в uintptr и обратно допустима только внутри одного выражения, а сохранение адреса в переменную работает случайно, до первого роста стека или ближайшей сборки мусора. Такой код проходит тесты и падает под нагрузкой.