Перейти к содержимому
Теория · Интерфейсы · урок 5 из 10

any и утверждение типа

В первом уроке темы интерфейс был объявлением о вакансии. А что, если в объявлении нет ни одного требования? Тогда подходит любой кандидат. Но и знает о нём работодатель ровно ничего: прежде чем дать новичку руль, придётся спросить: «Вы водитель?»

В Go такое объявление без требований — тип any. В переменную типа any можно положить значение любого типа: число, строку, срез. Но работать с ним как с числом нельзя, даже если внутри лежит число:

GO
package main

import "fmt"

func main() {
    var v any = 42
    fmt.Println(v + 1) // ✗
}
TEXT
invalid operation: v + 1 (mismatched types any and untyped int)

Компилятор знает только тип переменной v, а это any. При сборке он не знает, что во время выполнения в ней окажется int.

Пустой интерфейс

any — другое имя типа interface{}, интерфейса без единого метода. Такой интерфейс называют пустым. Его требованиям отвечает любой тип, поэтому any принимает любое значение. Два примера из жизни:

  • fmt.Println принимает аргументы типа any и потому печатает что угодно;
  • срез []any хранит значения разных типов вперемешку.

Динамический тип

Значение интерфейса помнит само значение и его конкретный тип. Этот тип называют динамическим. Знак %T печатает именно его:

GO
var v any = 42
fmt.Printf("%T\n", v) // int
v = "go"
fmt.Printf("%T\n", v) // string

Давайте разберём пример по строкам:

  1. Сначала в v лежит 42, и %T печатает int.
  2. После v = "go" в той же переменной лежит строка, и %T печатает string.

Тип самой переменной v при этом не меняется: он по-прежнему any. Меняется только динамический тип значения внутри.

Интерфейс и nil

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

Этот вопрос часто задают на собеседовании: равно ли nil значение интерфейса, в которое положили nil-указатель? Разберём по шагам:

  1. var p *int создаёт пустой указатель, и p == nil даёт true.
  2. var v any = p кладёт этот указатель в интерфейс.
  3. v == nil даёт false: указатель внутри пустой, но динамический тип *int в паре уже записан.

Нажимайте кнопку и смотрите, когда v == nil становится истинным, а когда нет.

ВживуюЗначение интерфейса: тип и значениешаг 0 из 3 · v == nil → true
var v any
var p *int // nil
x := 5

Пара v пока пустая: ни типа, ни значения. Нажмите кнопку.

Следите за коробкой «тип»

Утверждение типа

Утверждение типа v.(int) достаёт из интерфейса значение типа int. Это и есть вопрос «Вы водитель?». Чтобы им пользоваться без сюрпризов, запомните его правила:

  • если динамический тип v — действительно int, результат — обычное число, с которым работает арифметика;
  • утверждение ничего не преобразует, а только проверяет тип: из строки "42" число не получится;
  • применить его можно лишь к значению интерфейсного типа, а к переменной типа int — нельзя.

Здесь картинка с вакансией перестаёт работать. Живой кандидат может ответить «почти водитель», а утверждение типа требует точного совпадения. Для v с int внутри проверка v.(float64) не пройдёт, хотя оба типа — числа.

У утверждения две формы. Вот обе в одном примере:

GO
var v any = 42
n := v.(int)
fmt.Println(n + 1) // 43
s, ok := v.(string)
fmt.Printf("%q %v\n", s, ok) // "" false

Форма с одним результатом

Запись n := v.(int) возвращает только значение. Здесь в v лежит int, поэтому n получает число 42, и n + 1 печатает 43.

Эта форма годится, только когда вы точно знаете тип. Если тип не совпадёт, программа завершится паникой — это показывает раздел о частой ошибке.

Форма с двумя результатами

Запись s, ok := v.(string) возвращает значение и флаг, как чтение из map. Разберём пример по шагам:

  1. Утверждение проверяет, не строка ли лежит в v.
  2. Там int, а не string, поэтому ok равен false.
  3. Переменная s получает нулевое значение типа string, пустую строку "", и программа работает дальше.

Когда тип совпадает, ok равен true, а в s оказывается сама строка. Эту форму пишут, когда значение может оказаться другого типа.

Частая ошибка

Утверждение с одним результатом применили к значению другого типа:

GO
var v any = "42"
n := v.(int) // ✗
fmt.Println(n)
TEXT
panic: interface conversion: interface {} is string, not int

Программа компилируется, но при выполнении завершается паникой: в v лежит строка, а не int. Форма с одним результатом годится, только когда вы точно знаете тип. Если значение может оказаться другого типа, пишите n, ok := v.(int) и проверяйте ok.

Главное

  • any — пустой интерфейс interface{}: в него помещается значение любого типа.
  • Значение интерфейса — пара из динамического типа и значения; с nil-указателем внутри оно не равно nil.
  • v.(int) только проверяет тип и ничего не преобразует; при несовпадении форма с одним результатом вызывает панику.
  • Форма s, ok := v.(string) не паникует: при неудаче ok равен false, а s получает нулевое значение.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом