← Назад к вопросам

Что выведет код? Interface и nil

1.8 Middle🔥 181 комментариев
#Основы Go

Условие

Определите, что выведет следующий код:

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
}

Вопросы

  1. Что выведет программа: ERROR или NO ERROR?
  2. Объясните почему

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Это подвох с 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 — ошибка!
    // выполнится, хотя ошибки нет
}

Ключевые выводы

  1. nil и interface — разные вещи

    • nil — это переменная типа interface с (nil, nil)
    • *MyError(nil) — это переменная типа error с (*MyError, nil)
  2. Инициализируйте как interface, не как конкретный тип

    // ❌ Плохо
    var err *MyError
    
    // ✅ Хорошо
    var err error
    
  3. Всегда возвращайте nil явно

    // ❌ Плохо
    var result error
    return result  // может быть типизированный nil
    
    // ✅ Хорошо
    return nil     // истинный nil
    
  4. Linter ловит эту ошибку

    • golangci-lintgolint → находит такие проблемы
    • используй 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 коде.