Модуль
1Интерфейсы: основы2Стандартные интерфейсы: io.Reader, Stringer, error3Пустой интерфейс и any4Type assertion и type switch← вы здесь
Урок 4~12 минут

Type assertion и type switch

Type assertion: достаём значение из интерфейса

Когда переменная имеет интерфейсный тип, компилятор знает о ней только методы интерфейса — но не конкретный тип. Type assertion — это способ сказать компилятору: «я знаю, что здесь лежит вот этот конкретный тип».

go
var i interface{} = "hello"
 
// Двухзначная форма — безопасная
s, ok := i.(string)
fmt.Println(s, ok) // hello true
 
n, ok := i.(int)
fmt.Println(n, ok) // 0 false  (нулевое значение, ok=false)

Двухзначная форма никогда не паникует: если тип не совпадает, ok будет false, а переменная получит нулевое значение.


Опасная форма: без ok

Есть однозначная форма без ok:

go
s := i.(string) // ok, если i содержит string
n := i.(int)    // PANIC: interface conversion: interface {} is string, not int

Используйте её только когда абсолютно уверены в типе. На практике это редкость — предпочитайте двухзначную форму.

go
// Типичный паттерн
func getString(v any) (string, bool) {
    s, ok := v.(string)
    return s, ok
}

Type assertion с интерфейсом как целевым типом

Assertion работает не только на конкретные типы — можно проверить, реализует ли значение другой интерфейс:

go
type Stringer interface {
    String() string
}
 
type Writer interface {
    Write([]byte) (int, error)
}
 
func tryStringer(v any) {
    if s, ok := v.(Stringer); ok {
        fmt.Println("реализует Stringer:", s.String())
    } else {
        fmt.Println("не реализует Stringer")
    }
}

Это мощный паттерн: в fmt так и устроен вывод — fmt.Println проверяет, есть ли у значения метод String() string.


Type switch: несколько типов сразу

Когда нужно обработать много вариантов типов, цепочка if ok := v.(T) становится громоздкой. Type switch решает это элегантно:

go
func describe(i any) {
    switch v := i.(type) {
    case int:
        fmt.Printf("int: %d (удвоенное: %d)\n", v, v*2)
    case string:
        fmt.Printf("string: %q (длина: %d)\n", v, len(v))
    case bool:
        fmt.Printf("bool: %v\n", v)
    case []int:
        fmt.Printf("[]int: len=%d\n", len(v))
    case nil:
        fmt.Println("nil")
    default:
        fmt.Printf("неизвестный тип: %T\n", v)
    }
}

Важные особенности type switch:

  • v := i.(type) — специальный синтаксис, работает только в switch
  • В каждом case переменная v уже имеет конкретный тип ветки
  • Несколько типов в одном case: case int, int64:

Реальные паттерны

Обработка ошибок с дополнительным контекстом:

go
type NotFoundError struct {
    Resource string
}
 
func (e *NotFoundError) Error() string {
    return e.Resource + " not found"
}
 
func handleError(err error) {
    if nfe, ok := err.(*NotFoundError); ok {
        log.Printf("ресурс не найден: %s", nfe.Resource)
        return
    }
    log.Printf("общая ошибка: %v", err)
}

Опциональные интерфейсы (optional interface):

go
type Closer interface {
    Close() error
}
 
func closeIfPossible(v any) {
    if c, ok := v.(Closer); ok {
        c.Close()
    }
}

Так работает http.ResponseWriter — сервер проверяет, поддерживает ли конкретный ResponseWriter интерфейс http.Flusher или http.Hijacker.


Compile-time проверка интерфейса

Хотите убедиться, что тип реализует интерфейс прямо во время компиляции? Используйте идиому с _:

go
type MyWriter struct{}
 
func (w *MyWriter) Write(p []byte) (int, error) {
    return len(p), nil
}
 
// Compile-time assertion: *MyWriter должен реализовывать io.Writer
var _ io.Writer = (*MyWriter)(nil)

Если *MyWriter не реализует io.Writer — получим ошибку компилятора, а не панику в рантайме. Размещайте такие проверки рядом с объявлением типа — они служат документацией.


Итого: когда что использовать

ЗадачаИнструмент
Достать один конкретный типv, ok := i.(Type)
Обработать несколько типовswitch v := i.(type)
Проверить реализацию интерфейсаv, ok := i.(Interface)
Compile-time гарантияvar _ I = (*T)(nil)

Type assertion — хирургический инструмент. Если вы часто делаете assertion на один и тот же тип, подумайте: может, лучше сделать функцию принимающей конкретный тип или специализированный интерфейс?

Type assertion v.(T) достаёт конкретный тип из интерфейса. Двухзначная форма v, ok := i.(T) безопасна — без неё при ошибке получите panic.
interface{} box — choose stored value
var a interface{} = 42 // concrete type: int
Assert to type
two-value form — never panics
Click an assertion target above.
🎯
Миссия 1 из 4
Что произойдёт при i.(string), если i содержит int, и ok не используется?