Интерфейсы: определение, неявная реализация и 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 оправдан там, где тип значения принципиально неизвестен на этапе написания кода — например, в контейнерах разнотипных элементов или в функциях-обёртках. Если ты знаешь, какие типы будут использоваться, лучше объявить конкретный интерфейс с нужными методами — тогда компилятор работает на твоей стороне.
