Здравствуйте!
В прошлом уроке мы заложили фундамент, разобравшись с переменными, константами и строгой типизацией в Go. Вы узнали, что Go не прощает смешения типов в операциях с переменными и требует явных преобразований. Это может показаться строгим, особенно в сравнеии с гибкостью TypeScript. Однако сегодня мы исследуем мощную концепцию, которая привносит в эту строгость элегантную гибкость: нетипизированные константы (untyped constants).
Этот урок объяснит, почему код вроде math.Sqrt(2) компилируется без ошибок, хотя 2 выглядит как целое число, а функция ожидает float64. Мы разберем, как Go работает с числовыми литералами, и научимся осознанно применять явные преобразования типов там, где они действительно необходимы.
Проблема строгих типов на практике
Давайте начнем с наглядного примера. В прошлом уроке мы установили, что Go не позволяет выполнять арифметические операции между переменными разных числовых типов. Посмотрите на этот код и ошибку компиляции:

Эта строгость — осознанное проектное решение, которое предотвращает трудноуловимые ошибки, возможные в языках с неявным приведением типов. Но как же тогда быть с литералами? Почему мы можем написать x := 10 / 3.0, но не можем сделать то же самое с переменными a := 10 и b := 3.0? Ответ кроется в особом статусе констант.
Константы: типизированные и нетипизированные
В Go константы, в отличие от переменных, могут быть "нетипизированными". Это не значит, что у них нет типа вообще; это значит, что их тип еще не зафиксирован в строгой системе типов Go. Они существуют в своего рода "идеальном" пространстве значений высокой точности.
Прочтите вводную часть статьи Роба Пайка, одного из создателей Go, где он объясняет мотивацию такого дизайна.
Constants - The Go Programming Language
В этой статье от Роба Пайка, одного из создателей языка, объясняется философия, лежащая в основе работы с константами в Go. Прочтите введение, чтобы понять, какие проблемы в языках вроде C подтолкнули к созданию текущей модели в Go.
Найдите раздел Background: C и прочтите его. Он задает контекст, объясняя, почему в Go нет "обычных арифметических преобразований", как в C.
Самый простой способ понять разницу между типизированными и нетипизированными константами — на примере строк.
const typedHello string = "Hello"— это типизированная константа. Она имеет конкретный типstringи подчиняется всем правилам. Ее нельзя присвоить переменной другого типа, даже если он основан на строке (type MyString string).const untypedHello = "Hello"— это нетипизированная константа. У нее еще нет фиксированного типа Go. Это просто текстовое значение, которое может быть присвоено любой переменной, совместимой со строками.
Изучите этот ключевой концепт на примере строк в следующей части статьи.
Constants - The Go Programming Language
Этот раздел наглядно демонстрирует различие между типизированными и нетипизированными константами.
Прочтите раздел String constants. Обратите внимание на примеры, где присваивание typedHello переменной типа MyString вызывает ошибку, а присваивание нетипизированной константы — нет.
Тип по умолчанию (Default Type)
Когда же нетипизированная константа обретает свой тип? Это происходит только тогда, когда контекст этого требует. Например, в объявлении x := 100, переменная x должна получить конкретный тип. В этом случае нетипизированная константа 100 передает ей свой тип по умолчанию.
Тип по умолчанию определяется синтаксисом литерала:
- Целочисленные литералы (например,
10,0xFF) имеют тип по умолчаниюint. - Литералы с плавающей точкой (например,
3.14,1e6) имеют типfloat64. - Символьные литералы в одинарных кавычках (например,
'a') имеют типrune(синонимint32). - Логические литералы
trueиfalseимеют типbool.
Constants - The Go Programming Language
В этих разделах объясняется, как и когда нетипизированные константы получают свой тип по умолчанию.
Прочтите разделы Default type и Default type determined by syntax. Это центральная идея, объясняющая, как работает вывод типов для констант.
Мощь нетипизированных констант
Такой дизайн дает два огромных преимущества: гибкость и точность.
-
Гибкость: Нетипизированная числовая константа может быть использована везде, где ожидается числовой тип, если ее значение укладывается в диапазон этого типа.
var i int8 = 127— OK,127— нетипизированная константа, помещается вint8.var f float32 = 127— OK,127может быть представлена какfloat32.var i int8 = 128— Ошибка компиляции! Значение128выходит за пределы диапазонаint8(от -128 до 127).
-
Точность: Числовые константы в Go существуют в пространстве чисел с произвольной точностью. Они не ограничены 64-битными
float64илиint64. Вычисления с ними производятся компилятором с максимальной точностью, и только конечный результат, при присвоении переменной, усекается до нужного типа.
Это позволяет писать такой код:
const secondsInDay = 24 * 60 * 60 // Вычисляется на этапе компиляции
const hugeNumber = 1e1000 // Это валидная константа...
// var x = hugeNumber // ...но она не поместится ни в один стандартный тип
var y = hugeNumber / 1e999 // А так можно! Результат (10.0) помещается в float64
Этот подход объединяет числовые литералы всех видов (целые, дробные, комплексные) в единое пространство, где они ведут себя просто как числа.
Constants - The Go Programming Language
Эти разделы раскрывают практическую пользу нетипизированных констант, включая их высокую точность и гибкость.
Сначала прочтите раздел Floats, обращая внимание на примеры с Huge и math.Pi. Затем прочтите заключительный раздел Numbers, который суммирует всю концепцию.
Для дополнительного обзора этих концепций, включая высокоточные числа и "продвижение типов" (type promotion), вы можете посмотреть это короткое видео.
Constants in Go (Go Basics #5)
Это видео дает альтернативный взгляд на те же концепции, используя другие примеры и диаграммы.
Сначала посмотрите объяснение разницы между типизированными и нетипизированными константами. Затем изучите типы по умолчанию. Далее идет демонстрация высокоточных чисел. Наконец, обратите внимание на продвижение типов, которое объясняет, как компилятор выбирает итоговый тип для выражения из нескольких констант.
Явные преобразования типов
Гибкость нетипизированных констант заканчивается там, где начинаются переменные. Как только значение присвоено переменной, его тип строго фиксирован. Для выполнения операций между переменными разных числовых типов необходимо использовать явное преобразование типа в форме T(v), где T — целевой тип, а v — значение.
var a int = 10
var b int64 = 20
// sum := a + b // Ошибка: mismatched types int and int64
// Правильно:
sum := int64(a) + b
// или
sum2 := a + int(b)
Преобразование возможно только между совместимыми типами. Например, можно преобразовать int в float64, но нельзя потерять дробную часть при преобразовании float64 в int без явного намерения.
В этом ресурсе детально описаны правила явных преобразований.
Прочтите раздел Explicit Conversions of Untyped Constants. Сосредоточьтесь на примерах легальных и нелегальных преобразований. Обратите внимание, почему int(1.23) является ошибкой — компилятор защищает от неявной потери данных.
В качестве практического примера, посмотрите, как выполняется преобразование между int и int64 в следующем видеофрагменте.
Изучаем Go. Урок №2. Переменные, константы. Типы. Области видимости. Преобразование типов #golang
Этот отрывок демонстрирует необходимость и синтаксис явного преобразования типов при сложении переменных разных целочисленных типов.
Посмотрите фрагмент про преобразование примитивных типов. Автор наглядно показывает, почему nam1 + nam2 вызывает ошибку и как ее исправить с помощью int64(nam2).
Итоги
Давайте подведем итог. Сегодня мы разобрали один из самых элегантных аспектов системы типов Go.
- Нетипизированные константы — это "идеальные" значения (числа, строки, булевы), которые еще не имеют конкретного типа Go. Они существуют с произвольной точностью.
- Они получают тип по умолчанию (
int,float64и т.д.) только тогда, когда контекст (например, присваивание переменной:=) этого требует. - Эта гибкость позволяет писать естественный код (например,
math.Sqrt(2)) без лишних преобразований типов. Компилятор сам подставляет нужный тип для нетипизированной константы, если значение в него помещается. - Как только значение оказывается в переменной, его тип строго фиксирован. Для операций между переменными разных, но совместимых типов, необходимо явное преобразование
T(value).
Теперь вы понимаете тонкий баланс между строгостью и гибкостью в Go. Эта модель позволяет писать одновременно безопасный и лаконичный код.
На следующем уроке мы вернемся к оператору := и обсудим его применение в разных областях видимости, а также разберем распространенную ошибку, известную как "затенение переменных" (variable shadowing).