Структуры, указатели, методы и интерфейсы

Интерфейсы: определение, неявная реализация и type assertion

Содержание курса

Type assertion и пустой интерфейс any

Иногда нужно сделать обратное: получить из интерфейсной переменной конкретное значение определённого типа. Для этого есть type assertion.

var s Stringer = Dog{Name: "Rex"}
d := s.(Dog)
fmt.Println(d.Name) // Rex

s.(Dog) говорит: «я знаю, что внутри Dog — дай мне его». Если это так, d получает конкретное значение Dog. Если нет — программа завершается паникой вида:

interface conversion: main.Stringer is main.Cat, not main.Dog

(точный текст зависит от пакета и конкретных типов, но суть одна: Go сообщает, что ожидался один тип, а внутри оказался другой.)

Паника — плохой исход, когда тип заранее неизвестен. Поэтому есть безопасная форма с двумя возвращаемыми значениями:

d, ok := s.(Dog)
if ok {
    fmt.Println("это Dog:", d.Name)
} else {
    fmt.Println("это не Dog")
}

При ok == false переменная d получает нулевое значение типа Dog (пустую структуру), паники нет. Это стандартный идиоматичный способ проверить конкретный тип, когда ты не уверен.


Теперь про any. Это псевдоним interface{} — интерфейс без единого метода. Раз методов нет, любой тип автоматически его реализует. Переменная типа any принимает что угодно:

var x any
x = 42
x = "hello"
x = Dog{Name: "Rex"}

fmt.Println принимает аргументы именно как any (точнее, ...any), поэтому туда можно передать значение любого типа.

Однако у переменной типа any нет никаких предметных операций — компилятор видит только статический тип any и не разрешит, например, арифметику или вызов пользовательского метода без уточнения типа:

var x any = 100
fmt.Println(x + 1) // ошибка компиляции: operator + not defined on interface {}

Чтобы работать с конкретным значением, нужна type assertion. Безопасная форма сохраняет полный контроль:

var x any = 100
if n, ok := x.(int); ok {
    fmt.Println(n * 2) // 200
}

Однозначная форма без ok вернёт панику в рантайме, если динамический тип не совпадёт:

var x any = 100
s := x.(string) // panic: interface conversion: interface {} is int, not string
_ = s

Компилятор здесь промолчит — он не знает, что именно лежит внутри any во время выполнения. Ошибка появится только в рантайме.

Практическое правило: any оправдан там, где тип значения принципиально неизвестен на этапе написания кода — например, в контейнерах разнотипных элементов или в функциях-обёртках. Если ты знаешь, какие типы будут использоваться, лучше объявить конкретный интерфейс с нужными методами — тогда компилятор работает на твоей стороне.