Что выведет код? Interface и nil
Условие
Определите, что выведет следующий код:
package main
import "fmt"
func main() {
err := do()
if err != nil {
fmt.Println("ERROR")
} else {
fmt.Println("NO ERROR")
}
}
func do() error {
var p *MyError = nil
if false {
p = &MyError{"error"}
}
return p
}
type MyError struct {
msg string
}
func (e MyError) Error() string {
return e.msg
}
Вопросы
- Что выведет программа: ERROR или NO ERROR?
- Объясните почему
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Это подвох с nil в Go — одна из самых частых ошибок и источников confusion для разработчиков, особенно идущих из других языков. Ответ может удивить!
Ответ
ERROR
Программа выведет ERROR, хотя p явно равна nil и if false не выполняется.
Объяснение: nil в interface{} и error
Проблема в тонкой разнице между типизированным nil и истинным nil.
Как Go представляет interface
Каждый interface в Go — это пара (type, value):
interface{} = (type, value)
Когда вы присваиваете значение interface:
var p *MyError = nil // это nil указатель
err := error(p) // err = (*MyError, nil)
Важно: err НЕ равен nil в смысле interface! Это типизированный nil.
err = (*MyError, nil) // type = *MyError, value = nil
Сравнение err != nil проверяет не value, а всю пару:
if err != nil { // проверяет: тип не nil И value не nil
// true, если обе части ненулевые
}
Поскольку тип *MyError не nil (это конкретный тип), сравнение возвращает true.
Визуализация
var p *MyError = nil
err := error(p) // p = nil
err внутри:
┌─────────────┬──────┐
│ *MyError │ nil │
│ (type) │(value)│
└─────────────┴──────┘
↑
не nil!
err != nil // true, потому что тип (*MyError) != nil
err == nil // false
Почему это происходит?
В Go интерфейс — это структура данных с двумя полями:
type eface struct { // для interface{}
typ *rtype // тип значения
data unsafe.Pointer // указатель на значение
}
type iface struct { // для interface с методами
tab *itab // информация о типе и методах
data unsafe.Pointer // указатель на значение
}
Итерфейс считается nil только если оба поля nil:
if err == nil { // проверяет: tab == nil И data == nil
В нашем случае:
tab= информация о*MyError(не nil)data= nil
Поэтому err != nil возвращает true.
Как правильно обработать?
Вариант 1: Вернуть nil явно
func do() error {
var p *MyError = nil
if false {
p = &MyError{"error"}
}
if p == nil {
return nil // вернуть истинный nil
}
return p
}
*Вариант 2: Инициализировать как error, а не MyError
func do() error {
var err error = nil // инициализируем как error
if false {
err = &MyError{"error"}
}
return err // вернёт истинный nil
}
Вариант 3: Не использовать необязательные переменные
func do() error {
if false {
return &MyError{"error"}
}
return nil // явно возвращаем nil
}
Практический пример проблемы
func getData() error {
var dbErr *sql.Error
if !hasAccess {
// dbErr остаётся nil, но это типизированный nil
}
return dbErr // вернёт (*sql.Error, nil) — НЕ истинный nil!
}
if getData() != nil { // true — ошибка!
// выполнится, хотя ошибки нет
}
Ключевые выводы
-
nil и interface — разные вещи
nil— это переменная типа interface с (nil, nil)*MyError(nil)— это переменная типа error с (*MyError, nil)
-
Инициализируйте как interface, не как конкретный тип
// ❌ Плохо var err *MyError // ✅ Хорошо var err error -
Всегда возвращайте nil явно
// ❌ Плохо var result error return result // может быть типизированный nil // ✅ Хорошо return nil // истинный nil -
Linter ловит эту ошибку
golangci-lint→golint→ находит такие проблемы- используй linting в CI/CD
Внутреннее представление (advanced)
Можно вывести это через unsafe:
import (
"fmt"
"unsafe"
)
func main() {
var p *MyError = nil
var err error = p
// interface{} имеет internal структуру (type, value)
// err != nil, потому что type != nil
fmt.Printf("%v\n", err) // <nil> (выводит value)
fmt.Printf("%T\n", err) // *main.MyError (выводит type)
if err != nil {
fmt.Println("Интерфейс не nil")
}
}
Это — критическое знание для Go разработчика, часто спрашивается на собеседованиях и встречается в production коде.