Интерфейси и 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())