Menu

Утверждение типа в Golang: x.(T), comma-ok и type switch

Утверждение типа достаёт конкретное значение из интерфейса. x.(T), форма comma-ok, которая никогда не паникует, утверждение к другому интерфейсу, type switch и errors.As для обёрнутых ошибок.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

x.(T): достаём значение из интерфейса

Значение интерфейса скрывает конкретный тип, который в нём лежит. Утверждение типа x.(T) просит вернуть его.

Вывод:

gopher 6
0 false
string of length 6

x должен иметь интерфейсный тип. Утверждение на конкретном типе не компилируется: invalid operation: s (variable of type string) is not an interface.

Форма с одним значением паникует

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

Вывод:

recovered: interface conversion: interface {} is int, not string

Используйте форму с одним значением, только когда другой тип означает ошибку программиста, на которой лучше упасть. Во всех остальных случаях используйте comma-ok. При неудаче comma-ok даёт нулевое значение T и false.

Утверждение на nil-интерфейсе тоже не проходит: var v any; v.(string) паникует с interface conversion: interface {} is nil, not string, а форма comma-ok возвращает "", false.

Утверждение к другому интерфейсу

T может быть интерфейсным типом. Тогда утверждение успешно, если динамическое значение реализует T, и результат хранит то же самое динамическое значение. Так стандартная библиотека проверяет необязательные возможности.

io.Copy проверяет, реализует ли источник io.WriterTo, и если да, использует его. fmt проверяет Stringer и error. Этот шаблон позволяет маленькому интерфейсу оставаться маленьким, а вызывающему коду всё равно пользоваться более богатыми реализациями.

Type switch

Когда значение может быть одного из нескольких типов, type switch заменяет цепочку утверждений. x.(type) допустимо только внутри switch.

Вывод:

hi
integer 7
integer 8
3.14
nil
error: boom
unhandled []int

Что полезно знать:

  • В ветке с одним типом x имеет этот тип. В ветке с несколькими типами и в default x имеет тип исходного интерфейса (здесь any).
  • Ветки проверяются по порядку. Ставьте более конкретные интерфейсы раньше общих, ведь значение может удовлетворять нескольким.
  • case nil совпадает только с nil-интерфейсом, а не с интерфейсом, в котором лежит nil-указатель.
  • fallthrough в type switch нет.

errors.As: утверждение для обёрнутых ошибок

Ошибки часто оборачивают контекстом: fmt.Errorf("load config: %w", err). Прямое утверждение типа смотрит только на самую внешнюю ошибку и пропускает ту, что внутри. errors.As проходит по цепочке.

Вывод:

type assertion finds it: false
errors.As finds it: open /no/such/file

Применяйте утверждение типа к ошибкам, только если точно знаете, что ошибка не обёрнута, а на практике это почти никогда. errors.As, errors.Is и определение собственных типов ошибок разобраны на странице о собственных ошибках.

Утверждения, преобразования и дженерики

У вас естьНужноЧто использовать
intfloat64преобразование: float64(n)
any, в котором лежит intсам intутверждение: v.(int)
any одного из нескольких типовветвление по типуtype switch
error, который может быть обёрнутконкретный тип ошибкиerrors.As
функция для многих типовбезопасность на этапе компиляциидженерики

Код, полный any и type switch, часто говорит о том, что дженерики или нормальный интерфейс лучше выразили бы намерение и ловили бы ошибки на этапе компиляции.

Частые ошибки

  • Паникующая форма на непроверенных данных. Декодированный JSON, значения мапы типа any, входные данные плагинов: всегда comma-ok.
  • Утверждение к не тому числовому типу. Числа JSON декодируются в any как float64, поэтому v.(int) на них не срабатывает.
  • Утверждение к типу-значению, когда хранится указатель. Если в интерфейсе лежит *User, то v.(User) не пройдёт. Пишите v.(*User).
  • Утверждения типа на ошибках. Используйте errors.As.

Часто задаваемые вопросы

Что такое утверждение типа в Go?

Выражение x.(T), где x это значение интерфейса. Если T конкретный тип, оно возвращает хранящееся в x значение как T. Если T интерфейсный тип, оно проверяет, что хранящееся значение тоже реализует T. Форма с одним значением паникует, если проверка не прошла; форма с двумя значениями v, ok := x.(T) сообщает об этом через ok.

Как проверить тип значения интерфейса в Go?

Используйте type switch: switch v := x.(type) { case int: ...; case string: ...; default: ... }. Внутри каждой ветки v имеет тип этой ветки. Для одного типа короче утверждение comma-ok s, ok := x.(string). fmt.Printf("%T", x) печатает имя типа для отладки.

Чем утверждение типа отличается от преобразования типа в Go?

Преобразование T(x) превращает значение одного типа в другой, например float64(n); допустимо ли оно, решает компилятор, и во время выполнения оно не может завершиться неудачей. Утверждение x.(T) работает только со значениями интерфейсов и во время выполнения проверяет, какой тип хранится внутри. Для any, в котором лежит int, нельзя написать int(x); нужно x.(int).

Стоит ли проверять типы ошибок через утверждение типа?

Нет. Используйте errors.As(err, &target). Прямое утверждение err.(*MyError) смотрит только на внешнюю ошибку, поэтому перестаёт срабатывать, как только ошибку обернули через fmt.Errorf("...: %w", err). errors.As проходит по всей цепочке обёрток.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ