Почему вызов метода на nil-указателе в Go не приводит к падению программы, в отличие от Java?
Что проверяют
Понимаете ли вы, что метод это функция, которой получатель передаётся аргументом. Отсюда следует и поведение на нулевом указателе, и разница между двумя видами получателей, и главная ловушка языка с интерфейсом, внутри которого лежит нулевой указатель.
Как отвечать
Вызов метода на нулевом указателе сам по себе законен. У метода с получателем-указателем получатель это обычный аргумент, и если тело его не разыменовывает, паники не будет:
type List struct {
next *List
n int
}
func (l *List) Len() int {
if l == nil {
return 0
}
return 1 + l.next.Len()
}Рекурсия завершается сама, а проверка получателя внутри метода это нормальный приём для типов, у которых нулевой указатель означает пустое значение. Паника с сообщением про обращение по неверному адресу возникает ровно тогда, когда тело читает или пишет поле. У метода с получателем-значением паника будет всегда: чтобы получить копию, среда выполнения разыменует указатель ещё до входа в тело.
Отличие от Java не в устойчивости языка. Там вызов метода требует объекта, а здесь получатель передаётся явно, поэтому падение просто откладывается до первого обращения к полю.
Про интерфейс скажите отдельно: он хранит пару из типа и значения. Нулевой указатель конкретного типа, положенный в интерфейс, делает интерфейс не равным nil, хотя полезного значения внутри нет.
Где спотыкаются
Утверждают, что вызов на nil «ничего не делает». Делает: тело выполняется целиком, и первое же обращение к полю роняет программу. Разница с Java только в моменте падения.
Путают нулевой интерфейс и интерфейс с нулевым указателем внутри. Функция вернула нулевой указатель своего типа ошибки, проверка на nil сработала как истина, и обработчик разбирает ошибку, которой нет.
Превращают приём в правило и добавляют проверку получателя в каждый метод. Это осмысленно там, где пустой указатель действительно валидное состояние. В остальных случаях паника честнее молча пропущенной операции.