Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 12 из 25 ~18 мин +50 XP

Приведение типов и type assertion

type assertion go — запрос, который вводят, когда any уже исчерпал гостеприимство: в одиннадцатом уроке мы упаковывали что угодно в пару «динамический тип + значение», но доставать значения обратно компилятор не давал — у any нет ни одного метода, и арифметика над коробкой не собирается. Двенадцатый урок — инструменты распаковки. Одиночная форма x.(T): «утверждаю, внутри T» — и паника interface conversion, если утверждение ложно. ok-форма v, ok := x.(T): тот же вопрос без риска, с флагом удачи и нулевым значением при неудаче. type switch: переключение по динамическому типу, в каждой ветке значение снова конкретного типа. И развилка, на которой путаются все: чем утверждение x.(T) отличается от приведения числовых типов T(b) — операций с похожим видом и совершенно разной природой. Все примеры запускаются прямо на странице.

Содержание урока · 15 разделов

Type assertion в Go: как достать динамический тип из any#

В прошлом уроке мы закончили честным признанием: any — коробка, из которой компилятор не даёт ничего достать. Положить можно всё, вынуть — ничего: box[0] + 1 не собирается, v.String() не собирается, ошибку компилятор формулирует коротко — «у any нет методов». Стена нарочно высокая: она напоминает, что вы отдали компилятору зрение, когда упаковали значение в пару «динамический тип + значение».

В этом уроке мы возвращаем зрение — вручную, точкой и именем типа. Операция называется type assertion, утверждение типа, и записывается как x.(T): после выражения-интерфейса ставится точка и в скобках тип-претендент. Читается дословно: «утверждаю, что динамический тип значения, лежащего в x, — это T; отдай мне его как T». Если утверждение верно — вы получаете значение в его настоящем типе, и у него снова работают все операции: арифметика, методы, поля. Если неверно — поведение зависит от формы записи, и здесь начинается главное правило урока: у type assertion две формы, безопасная и опасная, и выбирать между ними нужно осознанно.

Type assertion — единственный штатный способ распаковать интерфейсное значение в Go. Дженериков вплоть до версии 1.18 не было, any был единственной корзиной «всё», и весь код стандартной библиотеки распаковывал её именно утверждением типа. Поэтому x.(T) вы встретите везде: в парсерах JSON, в драйверах баз данных, в обработчиках ошибок, в middleware веб-фреймворков, где из c.Request() достают конкретный контекст.

План урока: сначала ok-форма — безопасный вопрос «внутри T?», затем одиночная форма и её паника, затем распаковка []any-коробки из прошлого урока, проверка на интерфейсный тип и type switch — переключение по динамическому типу. Напоследок разграничим утверждение типа и приведение числовых типов — операции, которые новички постоянно путают из-за похожего вида. Интерактивы спроектированы под оба движка песочницы; где движки расходятся — честная оговорка.

Анатомия x.(T): вопрос, заданный паре «динамический тип + значение»#

Вспомним устройство из десятого урока: интерфейсная переменная — не контейнер с данными, а пара из двух слов — указателя на информацию о динамическом типе и указателя (или копии) самого значения. var v any = 42 — пара (int, 42); v = "семь" — пара (string, "семь"). Компилятор при упаковке тип знает и проверяет; после упаковки он его забывает — в этом смысл any. Type assertion — это операция, которая заглядывает в первую половину пары и сверяет её с вашим заявлением.

Синтаксис: x.(T), где x — выражение интерфейсного типа (any, error, fmt.Stringer, ваш собственный контракт), а T — тип-претендент: конкретный (int, string, ваша структура) или интерфейсный (об этом отдельно ниже). Результат операции — значение типа T, то есть после удачного утверждения вы работаете уже не с парой, а с конкретным значением: у него снова есть тип, операции, методы.

Форм две. ok-форма присваивает два значения: n, ok := v.(int) — первое это распакованное значение, второе — булев флаг удачи. Паники не бывает никогда: не совпало — получите нулевое значение T и ok == false. Одиночная форма присваивает одно значение: n := v.(int) — и верит вам на слово: если динамический тип не int, программа паникует с сообщением interface conversion. Первая форма — вопрос; вторая — заявление под ответственность.

Важная деталь, на которой спотываются: x обязан быть интерфейсного типа. Утверждение s.(string) над обычной строковой переменной не соберётся — компилятор и так знает её тип, утверждать нечего. Assertion — операция над парой, а пара есть только у интерфейсов. Отсюда и логика урока: упаковали через any — распаковывайте через assertion; у конкретных типов распаковывать нечего.

ok-форма: спросить и не паниковать

Запустите: первый вопрос — к паре (int, 42) — удачен, ok равен true, n получил значение. Второй вопрос — про string — неудачен: ok false, а s стал пустой строкой, потому что при неудаче ok-форма кладёт в переменную нулевое значение претендента. Обратите внимание на запись: s, ok объявлены через := при первом использовании, а дальше присваиваются обычным равенством — переменные уже существуют. Эксперимент: спросите у v про bool и float64 и посмотрите на флаг — он честно отвечает false, не заставляя программу падать.

Вывод

Запустите код, чтобы увидеть вывод.

ok-форма при неудаче: нулевое значение и флаг#

Разберём механику неудачи, потому что на ней построены все идиомы распаковки. Если динамический тип пары не равен T, ok-форма v, ok := x.(T) возвращает два честных факта: ok == false и v — нулевое значение типа T. Для int это 0, для string — пустая строка, для указателя — nil, для вашей структуры — структура с нулевыми полями. Нулевое значение — не «ошибка» и не «мусор»: это обычное корректное значение типа, которое вы обязаны игнорировать, пока флаг false. Отсюда железное правило: читайте флаг до значения. Идиома оформляется if-ом с инициализатором:

if n, ok := v.(int); ok { fmt.Println("целое:", n) }

Переменные n и ok живут только внутри ветки — компилятор не даст вам случайно использовать n за её пределами, где флаг не проверен. Это тот же приём, что if err != nil у функций: область видимости сужена до проверенного пути.

Отдельный случай — nil-источник. Если сама пара пуста (var v any), любой вопрос v.(T) в ok-форме отвечает ok == false — паники нет и здесь. Пустая пара не совпадает ни с каким типом, и ok-форма это фиксирует спокойно. Это делает ok-форму пригодной для обработки данных, которым вы не доверяете: входных JSON-ов, значений из внешних API, содержимого карт map[string]any — что бы ни лежало внутри, включая ничего, — программа не упадёт.

И тонкость, которую стоит проговорить: ok == false и «нулевое значение» — это одна ситуация, а не две. Не бывает «успеха с нулём» и «неудачи с нулём» одновременно — вопрос ок-формы всегда ровно один: совпал ли динамический тип. Поэтому нельзя полагаться на сравнение значения с нулём как на признак неудачи — единственный источник правды здесь флаг.

Одиночная форма v.(T): законные случаи и паника interface conversion#

Одиночная форма n := v.(int) — это заявление «я знаю, что внутри». Если заявление верно, форма короче и удобнее. Если нет — программа паникует: interface conversion. Настоящий Go формулирует панику исчерпывающе: panic: interface conversion: interface {} is string, not int — «в паре лежит string, а утверждали int». Сообщение читается как отчёт о преступлении: указан и настоящий динамический тип, и заявленный.

Когда одиночная форма законна? Тогда, когда динамический тип гарантирован логикой программы, а не надеждой. Три честных случая. Первый: вы сами только что упаковали значение и распаковываете его на соседней строке — тогда зачем вообще any, но если слои кода между упаковкой и распаковкой тип не теряли, утверждение верно. Второй: контракт функции обещает, что в any лежит конкретный тип, и это зафиксировано документацией и тестами — так устроены колбэки, где регистрация принимает any, а вызов знает, что передавал. Третий: выше по коду стоит проверка — type switch или ok-форма — и вы внутри проверенной ветки; правда, в этом случае идиома требует переиспользовать уже распакованное значение, а не утверждать повторно.

Анти-паттерн очевиден: «я уверен, что там int, проверять не буду». Уверенность в динамическом типе — это ставка на то, что все вызывающие во всех углах программы навсегда сохранят текущее поведение. Ставка проигрывает: рефакторинг меняет упаковщика, assertion в глубине роняет продакшн в три часа ночи. Паника interface conversion — не «дорога в аду», а честный сигнал, что где-то нарушение контракта между упаковкой и распаковкой; но сигнал дорогой, потому что срабатывает в рантайме, у пользователя.

Правило урока: по умолчанию ok-форма; одиночная форма — осознанное исключение с комментарием, откуда гарантия. В библиотечном коде паника иногда уместна как «невозможное состояние» — но тогда её стоит хотя бы задокументировать.

Проверьте себя

var v any = "не число"; n, ok := v.(int). Что произойдёт дальше?

Что выведет одиночная форма?

Прочитайте код и выберите, что он выведет:

package main

import "fmt"

func main() {
        var v any = "не число"
        n := v.(int)
        fmt.Println(n)
}

Распаковка коробки any из одиннадцатого урока: каскад ok-форм#

Вернёмся к коробке []any из прошлого урока — срезу, где в одной корзине лежали int, string и bool. Тогда мы могли её только печатать; теперь распакуем. План простой: идём циклом по срезу, для каждого элемента задаём серию ok-вопросов — «ты int?», «ты string?», «ты bool?» — и обрабатываем первый удачный. Идиома оформляется каскадом if-ов с инициализатором и continue:

if n, ok := v.(int); ok { fmt.Println(n); continue }

continue после обработки — важная деталь: элемент распакован, его вопрос закончен, идём к следующему. Без него каждый элемент проваливался бы во все последующие вопросы (безвредно — флаги false, — но расточительно и мутно).

Каскад ok-форм — рабочий инструмент, и в небольшом коде он честнее всего выглядит. У него есть два слабых места, о которых стоит знать заранее. Первое: элемент, не совпавший ни с одним претендентом, молча пропускается — float64 в коробке не напечатается вовсе; в данных, которым вы доверяете, молчаливый пропуск — потеря, поэтому ниже появится type switch с веткой default, которая ловит «всё остальное» громко. Второе: каскад растёт линейно с числом типов — пять типов, пять if-ов; type switch делает то же самое одной конструкцией. Но для двух-трёх типов каскад читается отлично и не требует выученного синтаксиса.

Третий нюанс каскада — порядок вопросов. Пары «динамический тип + значение» сравниваются с претендентом точно, поэтому порядок if-ов на результат не влияет: int не перепутается с int64, string с bool. Это не range-проверки, а точное равенство имён типов в паре — и к точности мы вернёмся, когда будем отличать assertion от преобразования.

Восстановите каскад распаковки

Впишите по одному типу-претенденту в каждый пропуск: вопросы идут в порядке int, string, bool.

package main import "fmt" func main() { box := []any{42, "семь", true} for _, v := range box { if n, ok := v.(); ok { fmt.Println("целое:", n) continue } if s, ok := v.(); ok { fmt.Println("строка:", s) continue } if b, ok := v.(); ok { fmt.Println("логическое:", b) } } }

Соберите ok-форму по шагам

Соберите программу: any со строкой внутри, ok-форма, печать значения после проверки флага.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Проверка на интерфейсный тип: v.(fmt.Stringer) и урок песочниц#

Претендент в x.(T) не обязан быть конкретным типом — им может быть интерфейс. Вопрос s, ok := v.(fmt.Stringer) звучит иначе, чем вопрос про int: не «ты строка?», а «умеешь ли ты печатать себя?». В настоящем Go удача означает: динамический тип пары реализует контракт целиком, и результат s — значение интерфейсного типа fmt.Stringer, готовое к вызову s.String(). Так проверяют не идентичность, а способность: у error-значения спрашивают err.(*MyErr) — «ты моя ошибка?», у любых значений — «ты Stringer?».

Именно ok-форма на error-пути — тот инструмент различения, который обещал одиннадцатый урок в истории с typed-nil. Напомнить связь одной фразой: ловушка была в том, что var e *MyErr = nil; return e даёт вызывающему пару (MyErr, nil) и обманывает `err != nil`. Ok-форма `me, ok := err.(MyErr)` — вежливый вопрос к interface-паре без паники: она различает «моя ошибка с полями» и «что-то чужое» не сравнением с nil, а проверкой динамического типа. В проде это главный способ достать свою структуру из generic-параметра error и прочитать её поля. Разбор самой ловушки не повторяем — он сделан в одиннадцатом уроке; здесь важно, что распаковка — её рабочее продолжение.

Теперь честный сюжет, ради которого тема интерфейсных претендентов не идёт в ранимые блоки. Наши обе песочницы на этом сюжете врут: код var v any = Cat{...} с методом String(); s, ok := v.(fmt.Stringer) обе отвечают ok == false, тогда как настоящий Go говорит true; одиночная форма s := v.(fmt.Stringer) паникует в обеих, хотя в настоящем Go проходит. Проверено пробниками до написания урока. Поэтому утверждение к интерфейсным типам разобрано здесь текстом и quiz-ом, а все ранимые блоки урока утверждают только к конкретным типам — на них оба движка точны. Сверяйте интерфейсные сюжеты локальным go run: это правило курса, и оно продолжает честность прошлых уроков.

ok-форма и своя ошибка: спросить, прежде чем звать

GO
package main

import "fmt"

// MyErr — своя ошибка с указательным методом Error() string
type MyErr struct{ msg string }

func (e *MyErr) Error() string { return e.msg }

// sendInvite возвращает либо *MyErr, либо голый nil — как в 11-м уроке
func sendInvite(ok bool) error {
        if !ok {
                return &MyErr{msg: "гость не подтвердился"}
        }
        return nil
}

func main() {
        err := sendInvite(false)

        // ok-форма: «вежливый вопрос» к паре error — ты ведь *MyErr?
        if me, ok := err.(*MyErr); ok {
                fmt.Println("своя ошибка:", me.msg)
        } else {
                fmt.Println("не моя или пустая:", err)
        }

        err = sendInvite(true)
        if me, ok := err.(*MyErr); ok {
                fmt.Println("своя ошибка:", me.msg)
        } else {
                fmt.Println("не моя или пустая:", err)
        }
}

Ok-форма различает свою ошибку в обёртке error без паники: в первой ветке динамический тип пары — *MyErr, флаг true, и поля структуры доступны; во второй — пара (nil, nil), флаг false, и мы печатаем err как есть. Блок статичный намеренно: путь error с настоящей ошибкой обе песочницы проходят верно (проверено пробником), но на пути typed-nil из одиннадцатого урока обе врут, поэтому весь error-сюжет курса живёт в статичных блоках и сверяется локальным go run. Ошибки со сквозной упаковкой %w и распаковкой через errors.As — дело пятнадцатого урока.

Проверьте себя ещё раз

У Cat есть метод String() string. В НАСТОЯЩЕМ Go: var v any = Cat{name: "Барсик"}; s, ok := v.(fmt.Stringer). Чему равно ok?

Что выведет каскад с незнакомым элементом?

Прочитайте код и выберите, что он выведет:

package main

import "fmt"

func main() {
        box := []any{"голос", 7, 3.5, true}
        for _, v := range box {
                if n, ok := v.(int); ok {
                        fmt.Println("целое:", n)
                        continue
                }
                if s, ok := v.(string); ok {
                        fmt.Println("строка:", s)
                        continue
                }
                if b, ok := v.(bool); ok {
                        fmt.Println("логическое:", b)
                }
        }
        fmt.Println("готово")
}

Type switch: переключение по динамическому типу#

Каскад ok-форм решает задачу «спросить про несколько типов», но у Go есть конструкция ровно для этого — type switch, переключение по динамическому типу. Синтаксис узнаёт обычный switch, но в скобках стоит не выражение, а утверждение с ключевым словом type:

switch x := v.(type) { case int: ... case string: ... default: ... }

Читается: «смотри на динамический тип пары, лежащей в v; если это int — работай в первой ветке, если string — во второй, иначе — в default». Главная прелесть конструкции — переменная x: в каждой ветке она уже конкретного типа. В case int компилятор видит int — легальна арифметика и %d; в case string — строка, легальна конкатенация; в default — x остаётся any. Это не «перегрузка» и не магия: switch разворачивается компилятором в каскад утверждений, и в каждой ветке x присваивается результат удачного v.(тип_ветки). Ничего лишнего не напишете: внутри case int вызов строкового метода x не соберётся.

Перечислим свойства конструкции точно. Веток может быть сколько угодно, включая свои структуры — case Cat: легален. default необязателен, но без него незнакомые типы проходят мимо, как в каскаде. Можно писать без переменной — switch v.(type) — когда значение в ветке не нужно, а важен только факт типа. Несколько типов в одной ветке перечисляются через запятую — case int, int64: — тогда x в этой ветке остаётся any: компилятор не знает, которое из двух выпало. И ещё: порядок case-ов, в отличие от if-каскадов с диапазонами, для точных совпадений не важен — вопрос всегда «равен ли динамический тип этому претенденту», а не «попадает ли в диапазон».

Канонический вопрос: когда type switch, а когда ok-форма? Один тип-претендент — ok-форма; два и больше, особенно с веткой «всё остальное», — type switch: та же семантика, меньше букв, громче незнакомые типы.

Type switch по трём типам и default

Запустите: четыре значения прошли через один switch — int поймал 42 и отформатировал %d, string склеил себя конкатенацией, bool распечатался через собственную ветку, а 3.14 провалился в default: динамический тип float64 не совпал ни с одним претендентом, и «всё остальное» прозвучало громко, без молчаливого пропуска. Эксперимент: допишите ветку case float64: return fmt.Sprintf("дробное %f", x) — и последний вызов перестанет попадать в default. Резервный движок песочницы конструкцию не исполняет — он отвечает «type switch (x.(type)) не поддерживается в песочнице; используйте type assertion x.(T)»; основной движок и локальный go run работают честно.

Вывод

Запустите код, чтобы увидеть вывод.

Приведение числовых типов: Т(b) — это не assertion#

Пришло время развилки, на которой путаются даже опытные: чем x.(T) отличается от T(b)? Внешность почти совпадает — скобка с типом; природа противоположная. Приведение типов (conversion) int(3.9), float64(i), string(rune) — операция над значением: компилятор берёт биты числа и перетолковывает их в новом типе, меняя представление — срезая дробную часть, расширяя или сужая разрядность. Она происходит полностью на стадии компиляции, у неё нет ни «динамического типа», ни флага удачи, ни паники. Для любой пары числовых типов она легальна всегда: int(3.9) даст 3, int64(i) даст то же число в широком типе, byte(300) даст 44 — с усечением по модулю, без вопросов.

Type assertion x.(T) — операция над парой: она ничего не перетолковывает и не преобразует, а смотрит на динамический тип и требует точного совпадения имён. В паре лежит int — вопрос x.(int64) провалится, хоть значения и совместимы: int и int64 — разные динамические типы, пары (int, 42) и (int64, 42) — разные пары. Провал ok-формы — флаг false; провал одиночной формы — паника. Компилятор здесь бессилен: он забыл динамический тип, проверка — рантайм.

Отсюда практическая таблица решений. Значение уже есть, нужно другое представление (целое ↔ дробное, ширина разрядности) — преобразование T(b): законно, компиляция, потеря точности на вашей совести. Значение лежит в интерфейсной паре и нужно обратно конкретным типом — assertion x.(T) с ok-формой. Между int и int64 через any: сначала assertion к родному типу — n, ok := x.(int) — потом преобразование int64(n), если нужно. Смешивать шаги нельзя: x.(int64) при паре (int, …) — ложь, а int64(пара) — вообще не собирается: преобразование не работает над интерфейсами, ему нужно значение.

И третья сила, живущая рядом: strconv.Atoi("42") — это ни то, ни другое, а парсинг: строка разбирается в число, ошибка приходит обычным err-ом. Если данные приходят текстом, вам нужен strconv, а не assertion — утверждение не умеет превращать строку в число, оно вообще ничего не превращает.

Преобразование чисел против type assertion

Запустите: верхняя половина — преобразование: int срезал дробную часть 3.9 до 3, float64 вернул число в дробном представлении — оба шага молча легальны, ни флагов, ни паник. Нижняя половина — assertion: в паре лежит (int, 42), поэтому вопрос про int64 проваливается с ok false и нулевым значением 0, а вопрос про int удачен. int и int64 — разные динамические типы, хотя диапазон значений int64 покрывает int: assertion точен до имени типа. Эксперимент: добавьте строку wide := int64(n2) — преобразование после удачной распаковки: так выглядит правильный маршрут int → int64 через any.

Вывод

Запустите код, чтобы увидеть вывод.

interface{} и приведение типов golang: сводка четырёх инструментов#

Соберём словарь урока в одну карту, потому что запрос приведение типов golang приводит на эту страницу сразу за несколькими словами, а в русском тексте «приведение типов» часто называют всё подряд: и conversion, и assertion, и парсинг. Разведём понятия окончательно.

Преобразование (conversion) — T(b) над значениями: числовые пары, именованные типы поверх базовых (type Celsius float64 и обратно), строка в срез байт. Компиляторная операция, всегда легальна для совместимых представлений, потеря точности возможна, динамический тип не участвует.

Утверждение (type assertion) — x.(T) над интерфейсной парой: извлечение динамического типа. Две формы: ok-форма без паники и одиночная с interface conversion. Работает только с интерфейсами: у конкретных значений пары нет, утверждать нечего.

Type switch — switch x := v.(type): синтаксическая упаковка серии утверждений, в каждой ветке x конкретного типа, default ловит незнакомцев.

Парсинг — strconv: текст в число и обратно, ошибка через error. Ни пара, ни преобразование тут не виноваты — это разбор формата.

Старая форма пустого интерфейса interface{} из прошлых уроков — тот же any: упаковка через него, распаковка через те же инструменты; исторически в старом коде вы встретите и switch x := v.(type), и v, ok := x.(int) в паре с interface{} — правила полностью совпадают. И общий критерий выбора, наследуемый от одиннадцатого урока: чем раньше значение перестаёт быть any, тем лучше — распаковывайте у границы и дальше несите конкретный тип; assertion — мост из мира незнания в мир гарантий, и чем короче мост, тем дешевле проверка. Четыре инструмента, четыре задачи: не занимайте их чужую работу.

Шпаргалка: инструменты вокруг пары «тип + значение»#

ИнструментЧто этоПравилоОсторожно
v, ok := x.(T)ok-форма утверждения: вопрос к динамическому типупаники нет никогда; при неудаче нулевое значение T и ok falseчитайте флаг до значения; идиома — if с инициализатором
n := x.(T)одиночная форма: заявление «внутри T»только когда тип гарантирован логикой и это задокументированопаника interface conversion: точные типы в сообщении
switch x := v.(type)type switch: перебор претендентов с веткой defaultв каждой ветке x конкретного типа; default — громкий уловрезервный движок песочницы не поддерживает
T(b)преобразование значений: другое представление числакомпиляция всегда для числовых пар; int(3.9) — 3не смотрит на динамический тип; над интерфейсом не собирается
strconv.Atoiпарсинг строки в числоэто не assertion: разбор формата, ошибка приходит err-омне путайте «достать тип» и «разобрать текст»
x.(*MyErr)проверка на свой тип ошибки в обёртке errorok-форма достаёт поля без паники; путь typed-nil — урок 11интерфейсные претенденты: песочницы врут, проверяйте go run

Правила гигиены type assertion#

  • По умолчанию ok-форма v, ok := x.(T); одиночная n := x.(T) — осознанное исключение с комментарием, откуда гарантия типа.
  • Идиома — if с инициализатором (if n, ok := x.(int); ok {...}): распакованное значение не живёт за пределами проверенной ветки.
  • Один претендент — ok-форма; два и больше — type switch с default: незнакомый тип должен попадать в громкую ветку, а не пропускаться молча.
  • int и int64 — разные динамические типы: assertion между ними проваливается; нужен другой размер — сначала распакуйте родной тип, затем преобразуйте T(b).
  • Преобразование T(b) — над значениями и всегда компилируется; assertion x.(T) — над парами и проверяется в рантайме: не подменяйте одно другим.
  • Распаковывайте any у границы системы и несите дальше конкретный тип; утверждение к интерфейсным претендентам (Stringer, error-типы) сверяйте локальным go run — песочницы на нём врут.

Что дальше: дженерики забирают работу у any#

Виток закрыт полностью: коробка any, которую одиннадцатый урок собрал, а десятый объяснил на паре «динамический тип + значение», научилась открываться. Ok-форма v, ok := x.(T) — безопасный вопрос с флагом; одиночная x.(T) — заявление под ответственность с паникой interface conversion; type switch — перебор претендентов с громким default; преобразование T(b) — соседняя, но другая операция над значениями. Плюс инструмент различения своей ошибки err.(*MyErr), продолжающий историю typed-nil. Контракт словаря прост: упаковка — по одну сторону точки, распаковка — по другую.

Что дальше по курсу? Тринадцатый урок — дженерики: type parameters и constraints. Это второй, более молодой ответ языка на ту же боль: «одна функция — много типов». Где any требовал распаковки assertion-ом и возвращал проверку в рантайм, дженерик func Map[T, R any](s []T, f func(T) R) []R держит типы в сигнатуре: компилятор знает их на этапе сборки, и ни пары, ни утверждения, ни паники не нужно. Двенадцатый урок дал инструменты распаковки, тринадцатый покажет, когда распаковка не нужна вовсе. Дальше — пакеты и модули (весь курс) и ошибки со сквозной упаковкой, где ok-форма к *MyErr из сегодняшнего статичного блока вырастет в полноценные errors.Is и errors.As.

Карточки урока

Термин

Type assertion x.(T) — что делает

нажмите, чтобы перевернуть
Определение

Заглядывает в первую половину интерфейсной пары «динамический тип + значение» и сверяет её с претендентом T. Совпало — возвращает значение в типе T (операции типа снова легальны). Работает только над интерфейсами: у конкретных значений пары нет.

1 / 6

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Type assertion x.(T) — извлечение динамического типа из интерфейсной пары; работает только над интерфейсами, у конкретных значений распаковывать нечего.
  • Ok-форма v, ok := x.(T) не паникует никогда: при несовпадении ok false, а v — нулевое значение T; идиома — if с инициализатором.
  • Одиночная форма n := x.(T) паникует interface conversion при несовпадении; законна, когда тип гарантирован логикой, и это задокументировано.
  • Каскад ok-форм распаковывает []any из прошлого урока; незнакомые типы он пропускает молча — type switch с default ловит их громко.
  • Type switch switch x := v.(type) выбирает ветку по динамическому типу; в каждой ветке x конкретного типа; претендентом может быть интерфейс.
  • Претендент-интерфейс проверяет способность, а не идентичность: v.(fmt.Stringer) в настоящем Go — ok true для Cat со String(); обе песочницы на этом врут — сверять go run.
  • Преобразование T(b) — компиляторная операция над значением без паник; assertion — рантайм-проверка пары: int и int64 — разные динамические типы.
  • err.(*MyErr) — ок-форма как инструмент различения своей ошибки, продолжение истории typed-nil; распаковывайте any у границы и несите конкретный тип дальше.

Домашнее задание

Соберите распаковщик заметок. Первый шаг: notes := []any{42, "текст заметки", true, 3.9} — пройдите циклом и распечатайте каждый элемент каскадом ok-форм (int/string/bool), убедитесь, что float64 молча пропущен; добавьте печать «незнакомый тип» через else в конце каскада и повторите. Второй шаг: перепишите каскад на type switch с веткой default — запустите локальным go run и сравните вывод. Третий шаг: функция sum(vals []any) int, которая складывает только int-элементы через ok-форму и игнорирует остальные; проверьте на срезе с примесями. Четвёртый шаг: повторите sendInvite и err.(*MyErr) из статичного блока в отдельном файле, соберите go build и убедитесь, что флаг ok работает на обоих вызовах; затем верните из функции (*MyErr)(nil) и проверьте, что err != nil — ловушка typed-nil из одиннадцатого урока, теперь различаемая ok-формой. Комментарии в коде — по-русски.

Личные заметкиПусто
Хранятся только в вашем браузере (Go Pro · Приведение типов и type assertion)

Вопросы и ответы по уроку

Чем type assertion отличается от приведения типов (conversion)?

Assertion x.(T) работает над интерфейсной парой: он ничего не преобразует, а сверяет динамический тип с претендентом и извлекает значение; проверка происходит в рантайме, несовпадение даёт флаг false (ok-форма) или панику (одиночная форма). Приведение T(b) работает над значением: перетолковывает представление (int(3.9) — 3), всегда компилируется для числовых пар, динамического типа у него нет вовсе.

Когда type assertion паникует и как этого избежать?

Паникует одиночная форма n := x.(T), если динамический тип пары не совпал с претендентом, а также при утверждении к пустой (nil) паре. Сообщение interface conversion называет оба типа. Избежать — ok-формой v, ok := x.(T): она никогда не паникует, при неудаче даёт ok false и нулевое значение T. Одиночная форма законна, когда тип гарантирован логикой программы.

Type switch или ok-форма: что выбрать?

Один тип-претендент — ok-форма с if-инициализатором. Два и больше претендентов — type switch: та же семантика, но переменная ветки сразу конкретного типа и есть ветка default, которая ловит незнакомые типы громко; каскад ok-форм незнакомцев пропускает молча. Несколько типов через запятую в одной ветке допустимы, но переменная ветки остаётся any.

Почему в паре лежит int, а x.(int64) даёт ok false?

Assertion сравнивает имена динамических типов точно: int и int64 — разные типы, пары (int, 42) и (int64, 42) различны. Assertion не преобразует размеры. Правильный маршрут: распаковать родной тип n, ok := x.(int), затем преобразовать int64(n). Преобразование, в отличие от assertion, свободно меняет ширину числового типа — но над интерфейсом оно не компилируется.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Fiber25 уроков~13 чпрактика25 уроков уже доступны

Fiber: экспресс-подобный веб-фреймворк на Fasthttp

Курс Fiber для Go: маршруты, middleware, JSON, CORS, WebSocket, шаблоны, лимитеры и тестирование. 25 уроков с примерами кода и итоговым REST API.

  • Поднимать HTTP-сервер на Fiber за минуты
  • Проектировать маршруты, группы и версии API
  • Писать middleware: логи, CORS, лимиты, recovery
  • Принимать и отдавать JSON с валидацией