Go: значения, типы, константы и iota

Go — статически типизированный язык: у каждого выражения есть тип, а операция допустима только для совместимых типов. Переменная хранит значение, имеет объявленный тип и всегда получает zero value до явного присваивания. Константа вычисляется во время компиляции и не является переменной; нетипизированная константа получает конкретный тип лишь там, где контекст этого требует.

Зачем это на интервью

Эта тема проверяет способность предсказывать поведение без запуска: что выведет код, почему var не равен :=, где переполнение обнаружится, чем type ID string лучше alias и что в действительности делает iota.

Минимум для E4

  • Различать объявление var, короткое объявление :=, явный тип и вывод типа.
  • Называть zero values: 0, false, "", nil для ссылочных представлений и нулевое значение структуры.
  • Отличать определённый тип (type UserID string) от alias (type UserID = string).
  • Объяснять, что iota — счётчик строк const-блока, начинающийся с нуля.
package main
 
import "fmt"
 
type UserID string       // новый определённый тип
type LegacyID = string   // alias: это тот же string
 
const (
	Read = 1 << iota // 1
	Write            // 2: повторяется выражение
	Admin            // 4
)
 
func main() {
	var count int        // 0
	name := "Ada"        // string, только внутри функции
	const limit = 1 << 60 // нетипизированная целая константа
	fmt.Println(count, name, Read|Write, limit)
}

:= разрешён только в функции и требует хотя бы одну новую переменную в текущей области видимости. В x, err := f() переменная err может быть уже объявлена, но хотя бы x должна быть новой.

Углубление для E5/Senior

Zero value — часть API-контракта. Хороший тип либо безопасно полезен без конструктора, либо явно запрещает некорректное состояние методами и документацией. Например, sync.Mutex годен сразу, но map в нулевом состоянии можно читать и удалять из него, однако запись вызовет panic. Не стоит проектировать API, в котором «нулевое значение» тихо означает опасный production-режим.

Нетипизированные числовые константы обладают произвольной точностью в рамках спецификации и могут быть присвоены представимому типу. Это удобно для time.Second * 3, но выражение становится ошибкой, когда значение не представимо: var x int8 = 128. Преобразование значения во время выполнения, напротив, может изменить его при переполнении согласно правилам целевого типа; проверку диапазона надо писать явно на границах протокола.

iota подходит для локальных стабильных наборов флагов. Он не создаёт enum: это константы без проверки исчерпывающего switch, без сериализационного контракта и без защиты от перестановки значений. Значения, хранимые в БД или передаваемые по сети, назначают явно; строковое представление и валидация должны быть сознательными.

Ключевые понятия

  • Объявленный и базовый тип — определённый тип имеет собственную идентичность даже при том же базовом типе.
  • Assignability — правило, по которому значение можно присвоить без явного преобразования; одинаковое представление не делает типы взаимозаменяемыми.
  • Преобразование — T(x) создаёт значение типа T; это не всегда проверка и не всегда безопасная операция.
  • Zero value — значение, которое получает память переменной до инициализации.
  • Константа — неизменяемое compile-time выражение базового типа boolean, numeric или string.
  • Нетипизированная константа — константа, чей конкретный тип выбирает контекст.
  • iota — предопределённый идентификатор, номер текущей спецификации в одном const (...).

Типовые вопросы

  1. Почему type ID string не то же, что type ID = string?
    • Первое вводит новый тип и защищает от случайного смешения с string; второе лишь даёт второе имя существующему типу.
  2. Можно ли написать в nil map?
    • Нет, присваивание вызывает panic. Чтение даёт zero value, delete безопасен, а для записи map надо создать через make или литерал.
  3. Что делает iota после пустой строки константы?
    • Счётчик увеличивается, а отсутствующее выражение повторяет предыдущее непустое выражение вместе с новым значением iota.
  4. Почему const x = 1 << 100 компилируется?
    • Это нетипизированная константа; ошибка появится, если присвоить её типу, который не может представить значение.
  5. Когда := не компилируется?
    • Вне функции или если в текущей области ни одна переменная не новая; тогда используйте = либо новое имя.
  6. Всегда ли conversion проверяет переполнение?
    • Нет. Преобразование числового значения следует правилам представления целевого типа; диапазон следует валидировать отдельно.

Практика

  • Определите type Currency string и константы трёх валют. Готово: функция валидации отвергает неизвестное значение, а Currency нельзя неявно передать туда, где нужен string.
  • Реализуйте набор битовых прав через iota. Готово: есть методы Has и String, тесты для комбинаций и документировано, что значения не меняются после релиза.
  • Напишите тесты для nil map, nil slice и zero value структуры. Готово: отдельно проверены чтение, append, запись и ожидаемый panic там, где он предусмотрен.

Частые ошибки и ловушки

  • Считать, что все zero values одинаково пригодны к мутации: nil map нельзя заполнять.
  • Использовать iota для публичного wire-формата и затем переставлять строки.
  • Смешивать alias и новый тип, теряя защиту доменного значения.
  • Не замечать shadowing при := во вложенном блоке.
  • Полагаться на неявное преобразование разных числовых типов: Go требует явности.

Связанные темы

Go · Основы языка Go · Массивы, срезы, map и строки · Функции, указатели и семантика значений

Источники