Почему вызов hello() на nil-указателе *gopher проходит успешно, а не паникует?
Прочитайте фрагмент ниже. Метод hello с указателем-получателем вызывается на nil-указателе *gopher.
Определите, что выведет программа, и объясните, почему вызов hello() на nil-получателе проходит успешно, а не паникует.
type gopher struct{ name string }
func (g *gopher) hello() { fmt.Println("hello works") }
func main() {
var g *gopher // nil
g.hello()
}
Определите вывод.
Метод с указателем-получателем — функция с указателем первым аргументом, поэтому вызов на nil-указателе допустим, пока тело не разыменовывает этот указатель. hello лишь печатает константу и не трогает g.name, поэтому отрабатывает. Обращение к g.name паникнуло бы с nil-разыменованием.
- ✗Считать, что любой вызов метода на
nil-указателе паникует в точке вызова - ✗Думать, что Go выделяет нулевое значение для
nil-получателя - ✗Забывать, что паника возникает, только когда тело разыменовывает
nil-указатель (например,g.name)
- →Какая именно ошибка runtime возникнет, если
helloпрочитаетg.nameнаnil-получателе? - →Как некоторые типы (например,
nil*Tree) намеренно полагаются на методы с nil-получателем?
Что выведет код?
type gopher struct{ name string }
func (g *gopher) hello() { fmt.Println("hello works") }
func main() {
var g *gopher // nil
g.hello()
}
Вывод
hello works
Почему вызов проходит
Метод с указателем-получателем func (g *gopher) hello() под капотом — это обычная функция hello(g *gopher). Вызов g.hello() просто передаёт g (здесь nil) первым аргументом.
Это легально: передать nil-указатель функции можно всегда. Паника возникает не от вызова, а от разыменования. hello лишь печатает константу и не обращается к g.name, поэтому ничего не разыменовывается — метод отрабатывает.
⚠️ Если бы тело читало поле через nil-указатель:
func (g *gopher) hello() { fmt.Println(g.name) } // g.name на nil
// panic: runtime error: invalid memory address or nil pointer dereference
Этот приём используют намеренно: например, методы на nil *Tree, где nil означает «пустое дерево».