any и утверждение типа
В первом уроке темы интерфейс был объявлением о вакансии. А что, если в объявлении нет ни одного требования? Тогда подходит любой кандидат. Но и знает о нём работодатель ровно ничего: прежде чем дать новичку руль, придётся спросить: «Вы водитель?»
В Go такое объявление без требований — тип any. В переменную типа any можно положить значение любого типа: число, строку, срез. Но работать с ним как с числом нельзя, даже если внутри лежит число:
package main
import "fmt"
func main() {
var v any = 42
fmt.Println(v + 1) // ✗
}invalid operation: v + 1 (mismatched types any and untyped int)Компилятор знает только тип переменной v, а это any. При сборке он не знает, что во время выполнения в ней окажется int.
Пустой интерфейс
any — другое имя типа interface{}, интерфейса без единого метода. Такой интерфейс называют пустым. Его требованиям отвечает любой тип, поэтому any принимает любое значение. Два примера из жизни:
fmt.Printlnпринимает аргументы типаanyи потому печатает что угодно;- срез
[]anyхранит значения разных типов вперемешку.
Динамический тип
Значение интерфейса помнит само значение и его конкретный тип. Этот тип называют динамическим. Знак %T печатает именно его:
var v any = 42
fmt.Printf("%T\n", v) // int
v = "go"
fmt.Printf("%T\n", v) // stringДавайте разберём пример по строкам:
- Сначала в
vлежит42, и%Tпечатаетint. - После
v = "go"в той же переменной лежит строка, и%Tпечатаетstring.
Тип самой переменной v при этом не меняется: он по-прежнему any. Меняется только динамический тип значения внутри.
Интерфейс и nil
Значение интерфейса — это пара: динамический тип и само значение. Интерфейс равен nil, только когда пусты обе части.
Этот вопрос часто задают на собеседовании: равно ли nil значение интерфейса, в которое положили nil-указатель? Разберём по шагам:
var p *intсоздаёт пустой указатель, иp == nilдаётtrue.var v any = pкладёт этот указатель в интерфейс.v == nilдаётfalse: указатель внутри пустой, но динамический тип*intв паре уже записан.
Нажимайте кнопку и смотрите, когда v == nil становится истинным, а когда нет.
var v any var p *int // nil x := 5
Пара v пока пустая: ни типа, ни значения. Нажмите кнопку.
Утверждение типа
Утверждение типа v.(int) достаёт из интерфейса значение типа int. Это и есть вопрос «Вы водитель?». Чтобы им пользоваться без сюрпризов, запомните его правила:
- если динамический тип
v— действительноint, результат — обычное число, с которым работает арифметика; - утверждение ничего не преобразует, а только проверяет тип: из строки
"42"число не получится; - применить его можно лишь к значению интерфейсного типа, а к переменной типа
int— нельзя.
Здесь картинка с вакансией перестаёт работать. Живой кандидат может ответить «почти водитель», а утверждение типа требует точного совпадения. Для v с int внутри проверка v.(float64) не пройдёт, хотя оба типа — числа.
У утверждения две формы. Вот обе в одном примере:
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. Разберём пример по шагам:
- Утверждение проверяет, не строка ли лежит в
v. - Там
int, а неstring, поэтомуokравенfalse. - Переменная
sполучает нулевое значение типаstring, пустую строку"", и программа работает дальше.
Когда тип совпадает, ok равен true, а в s оказывается сама строка. Эту форму пишут, когда значение может оказаться другого типа.
Частая ошибка
Утверждение с одним результатом применили к значению другого типа:
var v any = "42"
n := v.(int) // ✗
fmt.Println(n)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 и подпишись на канал — откроются все уроки.