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и при делении на ноль. - Какие типы можно сравнивать и использовать ключом мапы.