Почему функция с interface{} паникует на nil-указателе?

interface{} паникует на nil-указателе

Рассмотрим интересную задачу, которая часто встречается на собеседованиях Go-разработчиков. На первый взгляд, код выглядит совершенно корректно, но при запуске происходит паника. Почему? Потому что Go — строго типизированный язык, который хранит не только значение, но и его тип.

Давайте посмотрим, что нужно исправить в функции, чтобы исправить ошибку, не меняя ее сигнатуру.

Условие задачи

Перед нами функция printNumber, которая принимает пустой интерфейс (interface{}). Запускаем код и ловим панику:

package main

import "fmt"

func printNumber(ptrToNumber interface{}) {
    if ptrToNumber != nil {
        fmt.Println(*ptrToNumber.(*int))
    } else {
        fmt.Println("nil")
    }
}

func main() {
    v := 10
    printNumber(&v) // Ожидаем: 10
    var pv *int
    printNumber(pv) // Ожидаем: nil
    pv = &v
    printNumber(pv) // Ожидаем: 10
}

В чем подвох?

Проблема кроется в строке:

fmt.Println(*ptrToNumber.(*int))

Здесь используем type assertion (.(*int)), который говорит компилятору: «Я уверен, что внутри интерфейса лежит именно *int, дай доступ к нему».

Но давайте посмотрим, что реально хранится в интерфейсе в каждом вызове:

  1. printNumber(&v) — внутри *int ✅ (указатель на int)
  2. printNumber(pv) — внутри nil (но это не просто nil, это *int со значением nil) ❌
  3. printNumber(pv) — снова *int ✅

Второй вызов приводит к панике, потому что ptrToNumber.(*int) выполняется успешно (тип-то совпадает!), но когда пытаемся разыменовать указатель (*), Go получает nil-указатель и паникует.

Диагностика

Чтобы увидеть проблему своими глазами, добавим немного отладочной печати:

func printNumber(ptrToNumber interface{}) {
    if ptrToNumber != nil {
        fmt.Printf("Тип: %T, значение: %v\n", ptrToNumber, ptrToNumber)
        fmt.Println(*ptrToNumber.(*int))
    } else {
        fmt.Println("nil")
    }
}

Вывод покажет:

Тип: *int, значение: 0xc00008e0a8
10
Тип: *int, значение: <nil> // Вот он, nil-указатель!
panic: runtime error: invalid memory address or nil pointer dereferenced

Интерфейс не равен nil (потому что он хранит информацию о типе), но само значение внутри — nil.

Решение

Как исправить функцию, не меняя сигнатуру? Нужно проверить два условия:

  1. Интерфейс не пустой
  2. Внутри не nil-указатель

Вот элегантное решение:

func printNumber(ptrToNumber interface{}) {
    // Проверяем, что интерфейс не пустой
    if ptrToNumber == nil {
        fmt.Println("nil")
        return
    }

    // Пробуем получить значение как *int
    if val, ok := ptrToNumber.(*int); ok {
        if val != nil {
            fmt.Println(*val)
        } else {
            fmt.Println("nil")
        }
    } else {
        fmt.Println("ожидался *int")
    }
}

Но можно сделать еще лаконичнее:

func printNumber(ptrToNumber interface{}) {
    if ptrToNumber == nil {
        fmt.Println("nil")
        return
    }

    if val, ok := ptrToNumber.(*int); ok && val != nil {
        fmt.Println(*val)
    } else {
        fmt.Println("nil")
    }
}

Объяснение работы

  1. ptrToNumber == nil — проверяем, что интерфейс вообще пустой (в нем нет ни типа, ни значения)
  2. val, ok := ptrToNumber.(*int) — безопасный type assertion, который не паникует
  3. val != nil — проверяем, что сам указатель не нулевой

Теперь функция корректно обрабатывает все случаи:

  • printNumber(&v) → 10
  • printNumber(nil) → nil (пустой интерфейс)
  • printNumber(pv), где pv = nil → nil
  • printNumber(pv), где pv = &v → 10

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

В Go интерфейс, содержащий nil-указатель, не равен nil! Это одна из самых частых ловушек для новичков. Интерфейс считается nil только когда и тип, и значение равны nil.

Сталкивались ли вы на практике с подобным поведением интерфейсов? Может быть, у вас есть свои истории отладки, связанные с type assertion? Поделитесь в комментариях — интересно почитать ваш опыт.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: