Интерфейси и nil

Както вече казахме, интерфейсите са структура от две полета - указател към таблица с имплементация на методите на интерфейса и указател към данните. Този факт може да доведе до някои неочаквани резултати. След малко ще дам пример. Но нека първо ви запозная с вградения интерфейс error:

type error interface {
    Error() string
}

Той е част от езика и е прието методите, които връщат грешка, да връщат последна стойност от тип error:

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}

Както знаем, сотйността по подразбиране за променливи от някой интерфейс е nil. Така че съвсем очаквано следния код няма да направи нищо:

func main() {
    var err error
    if err != nil {
        fmt.Println(err.Error())
    }
}

Също така знаем, че стойността по подразбиране на указателите към структури също е nil. Тогава наивното предположение би било, че следния код също няма да прави нищо:

type MyError struct {
    message string
}

func (e *MyError) Error() string {
    return e.message
}

func main() {
    var me *MyError
    var err error = me
    if err != nil {
        fmt.Println(err.Error())
    }
}

На практика това води до паника заради използване на nil pointer:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xd3e86]

goroutine 1 [running]:
main.(*MyError).Error(0x0, 0x1741, 0x434070, 0xeee20)
    /tmp/sandbox612465983/main.go:12 +0x6
main.main()
    /tmp/sandbox612465983/main.go:26 +0x40

Защо!? Нали me е nil! Каква е разликата с предишния пример!? Разликата в това е, че err тук по време на проверката вече има стойност. Структурата е инициализирана и в таблицата на методите са сложени референции към методите на MyError. Значи тази структура съществува и е различна от nil. Поради тази причина се влиза в if-а и се изпълнява Error() на MyError типа. Данните, обаче все още липсват тъй като me е nil и получаваме nil pointer exception. Общо взето, горния код прилича на следното:

var me *MyError
fmt.Println(me.Error())