Урок по треку Go 6 мин

Типы и нулевые значения в Go

Базовые типы, нулевые значения, константы и iota, преобразования, переполнение, сравнимость.

Go строго и статически типизирован: тип каждой переменной известен при компиляции, а неявных преобразований между типами нет совсем. Даже int и int64 нельзя сложить без явного int64(x).

Базовые типы

  • Целые: int, int8 ... int64, uint, uint8 ... uint64, uintptr. Размер int и uint зависит от платформы: 64 бита на amd64 и arm64.
  • byte это псевдоним uint8, rune это псевдоним int32 (код символа Unicode).
  • Дробные float32, float64, комплексные complex64, complex128.
  • bool, string.
  • Составные: массивы, слайсы, мапы, структуры, указатели, функции, интерфейсы, каналы.

Нулевые значения

Объявленная без инициализации переменная никогда не содержит мусор, она получает нулевое значение своего типа:

var i int       // 0
var s string    // ""
var b bool      // false
var p *int      // nil
var sl []int    // nil, но len(sl) == 0 и append работает
var m map[string]int // nil: читать можно, писать нельзя (panic)
var u struct{ X int; Name string } // {0 ""}

Идиома Go: «делай нулевое значение полезным». sync.Mutex, bytes.Buffer, strings.Builder готовы к работе без конструктора.

Объявления

var a int = 1   // полная форма
var b = 2       // тип выведен
c := 3          // короткая форма, только внутри функций

:= объявляет хотя бы одну новую переменную слева. Во вложенном блоке она создаёт новую переменную, которая затеняет внешнюю. Это частый источник багов:

var err error
if true {
    x, err := strconv.Atoi("42") // новая err, внешняя осталась nil
    _ = x
}

Константы и iota

Константы вычисляются при компиляции. Нетипизированная константа (const big = 1 << 100) живёт с произвольной точностью, пока её не присвоят переменной конкретного типа.

type Weekday int
const (
    Sunday Weekday = iota // 0
    Monday                // 1
    Tuesday               // 2
)

const (
    _  = iota
    KB = 1 << (10 * iota) // 1024
    MB                    // 1048576
)

iota обнуляется в каждом новом блоке const и растёт на каждой строке спецификации, даже если строка его не использует.

Переполнение и деление

Целые переполняются молча, по модулю 2^n: var x int8 = 127; x++ даёт -128. А вот константное выражение с переполнением не скомпилируется: var y int8 = 128 это ошибка компиляции.

Целочисленное деление отбрасывает дробную часть к нулю: -7 / 2 == -3, -7 % 2 == -1. Деление на переменную со значением 0 вызывает panic, деление на константу 0 не компилируется. Для float деление на ноль даёт +Inf или NaN.

Преобразования и псевдонимы

type Celsius float64        // новый тип: нужен Celsius(x)
type MyInt = int            // псевдоним: это тот же int

Новый тип не наследует методы исходного, но может иметь свои. Преобразование string(65) даёт "A", а не "65" (go vet ругается). Для числа в строку нужен strconv.Itoa.

Сравнимость

== работает для чисел, строк, bool, указателей, каналов, интерфейсов, массивов и структур из сравнимых полей. Слайсы, мапы и функции сравнимы только с nil. Структура со слайсом внутри не может быть ключом мапы, это ошибка компиляции.

Частые ошибки

  • Ожидать, что string(i) превратит число в десятичную запись.
  • Забыть про затенение через := во вложенном блоке.
  • Писать в nil-мапу, объявленную через var m map[K]V.
  • Считать, что int всегда 32 бита.

Что спросят на собесе

  • Какие нулевые значения у разных типов и чем nil-слайс отличается от пустого.
  • Что такое iota и как сделать перечисление.
  • Чем type A B отличается от type A = B.
  • Что будет при переполнении int8 и при делении на ноль.
  • Какие типы можно сравнивать и использовать ключом мапы.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.