Go: nil-указатель в интерфейсе и вызов метода

В данном коде есть тонкий момент с интерфейсом и nil-значениями в Go. Переменная b объявлена как указатель на Bar, но не инициализирована, то есть равна nil.

Короткий ответ

Что ответить на собеседовании

В данном коде есть тонкий момент с интерфейсом и nil-значениями в Go. Переменная b объявлена как указатель на Bar, но не инициализирована, то есть равна nil.

Подробный разбор

Ответ с пояснениями

Условие

package main

import ( "fmt" )

type Foo interface { Foo() }

type Bar struct { Message string }

func (b Bar) Foo() { fmt.Println(b.Message) }

func main() { var b *Bar var f Foo f = b if f != nil { f.Foo() } }

Ответ

В данном коде есть тонкий момент с интерфейсом и nil-значениями в Go. Переменная b объявлена как указатель на Bar, но не инициализирована, то есть равна nil.

Когда мы присваиваем f = b, f становится интерфейсом, содержащим тип *Bar и значение nil.

Важно: интерфейс в Go считается nil только если и тип, и значение внутри интерфейса равны nil. В нашем случае тип *Bar не nil, а значение внутри nil.

Поэтому условие if f != nil будет истинным, и вызов f.Foo() произойдет. Но при вызове метода Foo() на b (который nil), метод Foo() определён с получателем по значению (func (b Bar) Foo()), а не по указателю. Вызов метода по значению на nil указателе невозможен и приведёт к панике.

Чтобы избежать паники, метод Foo() должен иметь получателя-указатель:

func (b *Bar) Foo() {
    if b == nil {
        fmt.Println("nil receiver")
        return
    }
    fmt.Println(b.Message)
}

Тогда вызов f.Foo() на nil указателе будет корректно обработан.

Итог: в Go интерфейс с nil значением внутри не равен nil, и вызов метода с получателем по значению на nil указателе приводит к панике.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее