Но те, кто не слишком хорошо знаком с Go, могут сильно удивиться, если узнают, что проверить ошибку на
nil тоже бывает не так-то просто. Дело в том, что тип error в Go - это на самом деле интерфейс. Под такой интерфейс подойдёт любой тип, в котором есть метод
Error(), возвращающий string. Утиная типизация. Этим довольно часто пользуются, когда надо сохранить какие-то детали ошибки, а не только некое текстовое сообщение. Создают свою структуру, приделывают к ней метод
Error(), и вуаля. Можно прозрачно её использовать в Go программе как обычную ошибку.
type CustomError struct {
When time.Time
What string
}
func (e *CustomError) Error() string {
return fmt.Sprintf("at %v, %s", e.When, e.What)
}
Однако есть нюанс. Функции обычно возвращают не конкретный тип, а интерфейс
error. И для переменных, которые имеют тип интерфейс, Go в рантайме хранит там два значеня: тип и value. Вот функция вернула интерфейс `
error`, который внутри функции был *CustomError, тогда она вернула 1) тип (например, *CustomError)
2) value (собственно сам указатель)
В случае с неинициализированным указателем это будет
1) тип *CustomError
2) nil
И в этом случае if err != nil не пройдёт, потому что Go чувствует, что это не простой nil, а хитрожопый: там внутре задан какой-то тип. В итоге имеем такой прикол:
package main
import (
"fmt"
"time"
)
type CustomError struct {
When time.Time
What string
}
func (e *CustomError) Error() string {
return fmt.Sprintf("at %v, %s", e.When, e.What)
}
func DoSomething() error {
// неинициализированный указатель - это nil
var err *CustomError
if 2+2 == 5 {
return &CustomError{When: time.Now()}
}
return err // казалось бы, возвращаем nil
}
func main() {
err := DoSomething()
if err == nil {
fmt.Println("Нет ошибки")
} else {
fmt.Println("Есть ошибка")
}
}
получаем "Есть ошибка", хотя ошибки не было
В данном случае лучше возвращать простой nil, не типизированный, т.е. вместо
return err написать return nilИ всё будет хорошо )
Cross Join - канал о разработке