Типы и Zero Values в Go
Глубокое погружение в систему типов Go: примитивы, размеры, нулевые значения, конвертация, константы, iota. Без понимания этого раздела все остальные темы (слайсы, мапы, интерфейсы) превращаются в магию. На собесе джуна — это базовый минимум, который проверяют первым.
Содержание (TOC)
Заголовок раздела «Содержание (TOC)»- Базовое определение и API
- Внутреннее устройство (под капотом)
- Тонкие моменты / Gotchas
- Производительность
- Типичные вопросы на собеседовании Junior
- Practice — задачки на проверку
- Источники и дополнительно
1. Базовое определение и API
Заголовок раздела «1. Базовое определение и API»1.1 Категории типов в Go
Заголовок раздела «1.1 Категории типов в Go»Go строго типизирован. Все типы делятся на:
- Boolean:
bool - Numeric: целые, числа с плавающей точкой, комплексные
- String:
string - Composite:
array,slice,map,struct,chan,interface,func,pointer - Named types (типы, определённые пользователем через
type)
1.2 Примитивные типы
Заголовок раздела «1.2 Примитивные типы»| Тип | Размер | Диапазон / Описание |
|---|---|---|
bool | 1 байт | true / false |
int8 | 1 байт | -128 .. 127 |
int16 | 2 байта | -32 768 .. 32 767 |
int32 | 4 байта | -2^31 .. 2^31-1 |
int64 | 8 байт | -2^63 .. 2^63-1 |
int | 4 или 8 байт | зависит от платформы (обычно 64 бита) |
uint8 (byte) | 1 байт | 0 .. 255 |
uint16 | 2 байта | 0 .. 65 535 |
uint32 | 4 байта | 0 .. 2^32-1 |
uint64 | 8 байт | 0 .. 2^64-1 |
uint | 4 или 8 байт | зависит от платформы |
uintptr | размер указателя | целое, в которое помещается указатель |
float32 | 4 байта | IEEE 754 single precision |
float64 | 8 байт | IEEE 754 double precision |
complex64 | 8 байт | float32 + float32i |
complex128 | 16 байт | float64 + float64i |
rune | 4 байта (int32) | Unicode code point |
string | 16 байт (на 64-bit) | заголовок: ptr + len |
package main
import ( "fmt" "unsafe")
func main() { var b bool var i int var s string var f float64
fmt.Println(unsafe.Sizeof(b)) // 1 fmt.Println(unsafe.Sizeof(i)) // 8 (на 64-bit) fmt.Println(unsafe.Sizeof(s)) // 16 fmt.Println(unsafe.Sizeof(f)) // 8}1.3 Zero values
Заголовок раздела «1.3 Zero values»В Go нет неинициализированных переменных — компилятор автоматически присваивает нулевое значение.
| Тип | Zero value |
|---|---|
bool | false |
int*, uint*, byte, rune | 0 |
float* | 0.0 |
complex* | 0+0i |
string | "" (пустая) |
pointer (*T) | nil |
| slice | nil (len=0, cap=0) |
| map | nil |
| channel | nil |
| function | nil |
| interface | nil |
| struct | все поля → zero |
array [N]T | все элементы → zero |
var i int // 0var s string // ""var p *int // nilvar sl []int // nil, len=0, cap=0var m map[string]int // nil
// struct: все поля zerotype Point struct { X, Y int Name string}var p2 Point // {X:0, Y:0, Name:""}1.4 Объявление переменных
Заголовок раздела «1.4 Объявление переменных»// 4 способа объявить переменнуюvar a int // zero valuevar b int = 10 // явный тип + значениеvar c = 10 // тип выводится (int)d := 10 // short, только внутри функций
// Множественноеvar x, y int = 1, 2a1, b1 := "hello", 42
// var-блокvar ( name string age int ok bool)⚠️ := работает только внутри функций. На package level нужно var.
2. Внутреннее устройство (ПОД КАПОТОМ)
Заголовок раздела «2. Внутреннее устройство (ПОД КАПОТОМ)»2.1 Память: как лежат значения
Заголовок раздела «2.1 Память: как лежат значения»Все примитивы Go — это просто байты в памяти. Компилятор знает размер и кодирование:
int64 = 8 байт little-endian (на x86/arm64): значение 1: 01 00 00 00 00 00 00 00 значение -1: FF FF FF FF FF FF FF FF (two's complement) значение 256: 00 01 00 00 00 00 00 00
float64 (IEEE 754): знак(1) | экспонента(11) | мантисса(52) 1.0: 00 00 00 00 00 00 F0 3F 0.1: 9A 99 99 99 99 99 B9 3F (НЕ точно 0.1!)2.2 string под капотом
Заголовок раздела «2.2 string под капотом»// runtime/string.go (упрощённо)type stringStruct struct { str unsafe.Pointer // указатель на байты UTF-8 len int // длина в БАЙТАХ}ASCII-схема:
string "Привет"┌──────────────┬──────┐│ ptr ─────────┼──┐ ││ len = 12 │ │ │ (6 символов × 2 байта в UTF-8)└──────────────┘ │ │ ▼ ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐ │ D0 │ 9F │ D1 │ 80 │ D0 │ B8 │ D0 │ B2 │ D0 │ B5 │ D1 │ 82 │ └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘ П р и в е т2.3 int vs intN: машинно-зависимый тип
Заголовок раздела «2.3 int vs intN: машинно-зависимый тип»// На 32-bit платформе: int = int32// На 64-bit платформе: int = int64
// Это НЕ алиас!var a int = 5var b int32 = 5// a == b // НЕ КОМПИЛИРУЕТСЯ — разные типыКогда использовать что:
int— почти всегда (индексы, счётчики)int64/int32— когда нужна гарантированная разрядность (binary protocols, hashes)uint— почти никогда; используется в специфичных случаях (битовые поля, размер памяти)
2.4 Type conversion vs Type assertion
Заголовок раздела «2.4 Type conversion vs Type assertion»Type conversion — преобразование значения между совместимыми типами (numeric, string ↔ []byte).
var i int = 42var f float64 = float64(i) // 42.0var b byte = byte(i) // 42
var s string = "hello"var bs []byte = []byte(s) // [104 101 108 108 111]Type assertion — извлечение конкретного типа из интерфейса.
var x interface{} = "hello"s := x.(string) // OK: s == "hello"s2, ok := x.(int) // ok == false (safe form)| Conversion | Assertion |
|---|---|
| Между типами | Из интерфейса в тип |
| Compile-time check | Runtime check |
T(v) | v.(T) |
| Panic не бросает | Может бросить panic |
2.5 Type alias vs Type definition
Заголовок раздела «2.5 Type alias vs Type definition»Type definition — новый тип, имеющий собственный набор методов.
type Celsius float64type Fahrenheit float64
var c Celsius = 100var f Fahrenheit = c // ОШИБКА компиляции: разные типыvar f2 Fahrenheit = Fahrenheit(c) // OKType alias — другое имя для того же типа (с Go 1.9).
type Celsius = float64 // ALIAS — это РОВНО тот же тип
var c Celsius = 100var f float64 = c // OK, никаких преобразований⚠️ Type alias использовался для миграции byte = uint8, rune = int32, и при рефакторинге пакетов (например, os/signal ↔ syscall).
2.6 iota — генератор констант
Заголовок раздела «2.6 iota — генератор констант»iota — это специальный идентификатор, существующий только внутри const-блоков. Он сбрасывается в 0 в начале каждого const-блока и увеличивается на 1 на каждой строке (ConstSpec).
const ( A = iota // 0 B // 1 (повторяется выражение) C // 2)Хитрые случаи:
// Пропуск значенийconst ( _ = iota // 0 — отбрасываем KB = 1 << (10 * iota) // 1 << 10 = 1024 MB // 1 << 20 GB // 1 << 30 TB // 1 << 40)
// Битовые флагиtype Flags uintconst ( FlagRead Flags = 1 << iota // 1 FlagWrite // 2 FlagExec // 4)
// Сброс на каждом const-блокеconst ( X = iota // 0)const ( Y = iota // 0 (НОВЫЙ блок!))⚠️ Подвох: Использование iota в выражениях с лишними строками.
const ( Bit0 = 1 << iota // 1 << 0 = 1 Bit1 // 1 << 1 = 2 _ // пропустили (iota == 2) Bit3 // 1 << 3 = 8)2.7 Untyped constants — почему это супер-сила Go
Заголовок раздела «2.7 Untyped constants — почему это супер-сила Go»const Pi = 3.14159 // untyped (default float64)const MaxItems = 100 // untyped (default int)
var f float32 = Pi // OK! Untyped автоматически конвертируетсяvar i int8 = MaxItems // OK, если влезает в диапазон
// Точность untyped — выше float64const Big = 1 << 100 // OK! Хотя в int64 не влезаетvar x int = Big // ОШИБКА: 1<<100 не влезает в intvar x2 float64 = Big // OK (потеря точности, но без overflow)Untyped constants имеют произвольную точность на этапе компиляции. Это позволяет:
- Писать
const c = 1.0/3.0с максимальной точностью. - Использовать одну константу с разными типами без приведений.
const N = 100var a int = Nvar b int64 = Nvar c float64 = N// Все три валидны без N(...)2.8 Comparable constraint и сравнения
Заголовок раздела «2.8 Comparable constraint и сравнения»Не все типы сравнимы через ==:
| Тип | Comparable? |
|---|---|
bool, числа | Да |
string | Да |
| pointer | Да |
array [N]T | Если T comparable |
| struct | Если все поля comparable |
| interface | Да (с runtime panic если динамический тип non-comparable) |
| slice | Нет! (только с nil) |
| map | Нет! (только с nil) |
| func | Нет! (только с nil) |
var s1 = []int{1, 2, 3}var s2 = []int{1, 2, 3}// s1 == s2 // ОШИБКА компиляцииs1 == nil // OK
type S struct{ data []int }var a, b S// a == b // ОШИБКА: struct содержит sliceВ generics с Go 1.18 появился constraint comparable:
func Index[T comparable](slice []T, target T) int { for i, v := range slice { if v == target { return i } } return -1}С Go 1.20 был расширен набор типов, удовлетворяющих comparable (включая интерфейсы, где compare может паниковать в runtime).
3. Тонкие моменты / Gotchas
Заголовок раздела «3. Тонкие моменты / Gotchas»Gotcha 1: Integer overflow
Заголовок раздела «Gotcha 1: Integer overflow»В Go нет паники при overflow целочисленных типов. Просто wrap-around.
var i int32 = 2_147_483_647i++fmt.Println(i) // -2147483648 — wrap around!
var u uint8 = 255u++fmt.Println(u) // 0⚠️ Это особенно опасно при работе с временем (time.Duration — int64 наносекунд) и хешированием.
Gotcha 2: Float precision
Заголовок раздела «Gotcha 2: Float precision»fmt.Println(0.1 + 0.2) // 0.30000000000000004fmt.Println(0.1+0.2 == 0.3) // false
// Правильное сравнение:const eps = 1e-9if math.Abs(a-b) < eps { /* ... */ }Gotcha 3: NaN — самый странный float
Заголовок раздела «Gotcha 3: NaN — самый странный float»nan := math.NaN()fmt.Println(nan == nan) // false (!!)fmt.Println(math.IsNaN(nan)) // true (используйте это)NaN не равен ничему, включая себя. Это нарушает рефлексивность в map (NaN-ключ нельзя достать).
m := map[float64]int{}m[math.NaN()] = 1m[math.NaN()] = 2fmt.Println(len(m)) // 2 (!)fmt.Println(m[math.NaN()]) // 0 — нельзя достатьGotcha 4: Конвертация в меньший тип — потеря данных без warning
Заголовок раздела «Gotcha 4: Конвертация в меньший тип — потеря данных без warning»var i int = 300var b byte = byte(i)fmt.Println(b) // 44 (300 % 256 = 44)Компилятор НЕ предупреждает. Используйте проверку или math.MaxInt8 и т.д.
Gotcha 5: nil interface vs interface с nil значением
Заголовок раздела «Gotcha 5: nil interface vs interface с nil значением»type MyError struct{}func (e *MyError) Error() string { return "err" }
func bad() error { var e *MyError = nil return e // возвращаем "пустой" указатель в интерфейсе}
func main() { err := bad() fmt.Println(err == nil) // FALSE!}Интерфейс — это пара (type, value). Здесь type = *MyError, value = nil. Сам интерфейс — НЕ nil.
✅ Правило: возвращайте nil напрямую, не присваивайте typed nil в interface.
Gotcha 6: Short declaration в новом scope
Заголовок раздела «Gotcha 6: Short declaration в новом scope»x := 10if true { x := 20 // НОВАЯ переменная в новом scope fmt.Println(x) // 20}fmt.Println(x) // 10⚠️ Особенно коварно с err :=:
err := doSomething()if cond { err := tryAgain() // СОЗДАНА новая err! Внешняя не обновлена. _ = err}// здесь err — это первое errGotcha 7: Multiple assignment с :=
Заголовок раздела «Gotcha 7: Multiple assignment с :=»:= требует, чтобы хотя бы одна переменная слева была новой.
a, err := f1()b, err := f2() // OK: b — новая, err переиспользуетсяa, err := f3() // ОШИБКА: ни a, ни err не новые
// Решение:a, err = f3()Gotcha 8: Constant overflow в типизированных контекстах
Заголовок раздела «Gotcha 8: Constant overflow в типизированных контекстах»const X = 1 << 62 // OK как untypedvar i int64 = X // OK
const Y = 1 << 63 // OK как untyped (не влезет в int64, но это untyped)var i64 int64 = Y // ОШИБКА: overflowGotcha 9: rune vs byte литерал
Заголовок раздела «Gotcha 9: rune vs byte литерал»var b byte = 'A' // 65, ASCIIvar r rune = 'Ё' // 1025, Unicode code pointvar x byte = 'Ё' // ОШИБКА: значение не влезает в byte
// Строковый литерал — это string, не []bytes := "A" // stringb2 := 'A' // rune (int32) (!!)⚠️ 'A' — это rune-литерал, а не byte. Зависит от контекста.
Gotcha 10: Сравнение интерфейсов с разными конкретными типами
Заголовок раздела «Gotcha 10: Сравнение интерфейсов с разными конкретными типами»var i1 interface{} = int(1)var i2 interface{} = int32(1)fmt.Println(i1 == i2) // false (разные конкретные типы!)Gotcha 11: Untyped constant и формат
Заголовок раздела «Gotcha 11: Untyped constant и формат»const N = 5fmt.Printf("%T\n", N) // ОШИБКА: cannot use N as untyped// нужно либо int(N), либо typed constВ большинстве случаев untyped становится typed автоматически (default type). Но через рефлексию untyped не виден.
Gotcha 12: bool — это не int
Заголовок раздела «Gotcha 12: bool — это не int»В отличие от C/Python, в Go true нельзя использовать как 1.
b := true// i := int(b) // ОШИБКА компиляцииi := 0if b { i = 1 }4. Производительность
Заголовок раздела «4. Производительность»4.1 Размеры структур и выравнивание
Заголовок раздела «4.1 Размеры структур и выравнивание»Из-за выравнивания (alignment), порядок полей в struct влияет на размер.
type Bad struct { a byte // 1 + 7 padding b int64 // 8 c byte // 1 + 7 padding} // = 24 байта
type Good struct { b int64 // 8 a byte // 1 c byte // 1 + 6 padding в конце} // = 16 байт✅ Правило: сортируйте поля от больших к меньшим.
import "unsafe"fmt.Println(unsafe.Sizeof(Bad{})) // 24fmt.Println(unsafe.Sizeof(Good{})) // 16Утилита fieldalignment (golang.org/x/tools) проверяет порядок.
4.2 Escape analysis
Заголовок раздела «4.2 Escape analysis»Компилятор решает, где разместить переменную: stack или heap.
go build -gcflags="-m" main.gofunc makeInt() *int { x := 42 return &x // x escape to heap (адрес уходит наружу)}
func sumLocal() int { x := 42 return x // stay on stack}✅ Локальные значения примитивов — на стеке (быстро). ⚠️ Указатели, замыкания, интерфейсы — часто на куче.
4.3 Untyped constants в hot path
Заголовок раздела «4.3 Untyped constants в hot path»const factor = 0.5
// Untyped — компилятор подставит без преобразованийvar f float32 = 100f *= factor // OK, factor — untyped float4.4 Битовые операции вместо умножения/деления
Заголовок раздела «4.4 Битовые операции вместо умножения/деления»// Быстрееx := n << 1 // n * 2y := n >> 3 // n / 8
// Медленнее (но обычно компилятор сам оптимизирует)x := n * 2y := n / 8⚠️ Сдвиг даёт результат, отличный от деления для отрицательных чисел (округление к -∞ vs к нулю).
4.5 Избегайте лишних типовых конверсий в циклах
Заголовок раздела «4.5 Избегайте лишних типовых конверсий в циклах»for i := 0; i < 1000; i++ { x := float64(i) // вычисляется каждый раз}Компилятор обычно справляется, но в горячем коде стоит проверить через -S (ассемблер).
5. Типичные вопросы на собеседовании Junior
Заголовок раздела «5. Типичные вопросы на собеседовании Junior»Q1: Какой размер у int в Go?
Заголовок раздела «Q1: Какой размер у int в Go?»A: Зависит от платформы: 32 бита на 32-битных, 64 бита на 64-битных системах. Это не алиас для int64. Если нужна гарантированная разрядность — int32/int64.
Q2: Чему равно zero value для string?
Заголовок раздела «Q2: Чему равно zero value для string?»A: Пустая строка "". Длина 0. Под капотом — stringStruct{str: nil, len: 0}. Сравнение s == "" валидно.
Q3: Что такое zero value для slice?
Заголовок раздела «Q3: Что такое zero value для slice?»A: nil. Под капотом: ptr=nil, len=0, cap=0. Можно делать append к nil-слайсу — работает. Можно делать len(nil_slice) — вернёт 0. Нельзя индексировать.
Q4: Какая разница между byte и rune?
Заголовок раздела «Q4: Какая разница между byte и rune?»A: byte — алиас для uint8 (1 байт, 0..255). rune — алиас для int32 (4 байта, Unicode code point). byte — для бинарных данных, rune — для символов Unicode.
Q5: Что выведет код?
Заголовок раздела «Q5: Что выведет код?»var b boolfmt.Println(b)A: false. Zero value для bool.
Q6: Можно ли сравнить два слайса через ==?
Заголовок раздела «Q6: Можно ли сравнить два слайса через ==?»A: Нет (compile error). Только с nil. Для поэлементного сравнения — slices.Equal() (Go 1.21+) или reflect.DeepEqual.
Q7: Что такое untyped constant?
Заголовок раздела «Q7: Что такое untyped constant?»A: Константа без явного типа. Имеет неограниченную точность и default type. Может использоваться с разными типами без явного преобразования: const N=100; var f float64 = N; var i int = N.
Q8: Разница между type A B и type A = B?
Заголовок раздела «Q8: Разница между type A B и type A = B?»A:
type A B— новый тип, требует явной конвертации, может иметь свои методы.type A = B— alias, синоним. Это тот же самый тип, никакого преобразования не нужно.
Q9: Что такое iota и в чём подвох?
Заголовок раздела «Q9: Что такое iota и в чём подвох?»A: Счётчик внутри const-блока, начинается с 0, инкрементируется на каждой строке ConstSpec. Подвох: сбрасывается между const-блоками; пропуски через _ тоже инкрементируют его; в выражениях может давать неочевидные значения.
Q10: Чему равно nil == nil?
Заголовок раздела «Q10: Чему равно nil == nil?»A: Зависит от контекста. var s []int; s == nil → true. Но var i interface{} = (*int)(nil); i == nil → false, потому что interface содержит type info. Это самая популярная ловушка про nil.
Q11: Что выведет?
Заголовок раздела «Q11: Что выведет?»const x = 1 << 30fmt.Printf("%T\n", x)A: int. Untyped constant, default type для целого — int. (Если использовать в float-контексте, может быть float64.)
Q12: Что такое overflow в Go и как с ним?
Заголовок раздела «Q12: Что такое overflow в Go и как с ним?»A: Целые числа wrap around (не panic). var x int8 = 127; x++ → -128. Защита: использовать большие типы или проверки math.MaxInt, math.MinInt.
Q13: Что такое NaN?
Заголовок раздела «Q13: Что такое NaN?»A: Not-a-Number из IEEE 754. math.NaN(). Особенность: NaN != NaN (даже сам с собой). Проверка только через math.IsNaN(x). В map может создать “недостижимые” ключи.
Q14: Как преобразовать int в string?
Заголовок раздела «Q14: Как преобразовать int в string?»A:
string(65)→"A"(Unicode code point, это gotcha!)strconv.Itoa(65)→"65"(правильно для числа)- С Go 1.15
go vetругается наstring(int)для не-rune.
Q15: Можно ли сложить int и float64?
Заголовок раздела «Q15: Можно ли сложить int и float64?»A: Нет. var i int = 5; var f float64 = 1.0; i + f — ошибка. Нужна явная конверсия: float64(i) + f.
Q16: Что выведет?
Заголовок раздела «Q16: Что выведет?»type Celsius float64var c Celsius = 100var f float64 = cA: Ошибка компиляции. Celsius — новый тип, нужен float64(c).
Q17: Чем uintptr отличается от unsafe.Pointer?
Заголовок раздела «Q17: Чем uintptr отличается от unsafe.Pointer?»A:
uintptr— целое число, в которое влезает указатель. GC его не отслеживает.unsafe.Pointer— настоящий указатель, GC видит, но проверка типов отключена.
Использовать uintptr для хранения адреса опасно — GC может переместить объект.
Q18: Что такое comparable в generics?
Заголовок раздела «Q18: Что такое comparable в generics?»A: Constraint для типов, поддерживающих ==/!=. С Go 1.18. С Go 1.20 расширен: интерфейсы тоже удовлетворяют (но могут паниковать в runtime, если внутри лежит non-comparable). Используется в map-ключах, slices.Index и т.д.
Q19: Какой тип у 'A'?
Заголовок раздела «Q19: Какой тип у 'A'?»A: rune (то есть int32). Значение 65.
Q20: Что выведет?
Заголовок раздела «Q20: Что выведет?»var a int = 10b := &a*b++fmt.Println(a)A: 11. *b++ инкрементирует значение, на которое указывает b. (Эквивалент *b = *b + 1.)
Q21: Можно ли иметь zero size struct?
Заголовок раздела «Q21: Можно ли иметь zero size struct?»A: Да, struct{} имеет размер 0 байт. Используется как маркер: map[K]struct{} — set, chan struct{} — сигнальный канал.
fmt.Println(unsafe.Sizeof(struct{}{})) // 0Q22: В чём разница между nil и 0?
Заголовок раздела «Q22: В чём разница между nil и 0?»A: nil — отсутствие значения для ссылочных типов (pointer, slice, map, chan, func, interface). 0 — нулевое значение для числовых типов. Они не взаимозаменяемы: var i int = nil — ошибка.
Q23: Что выведет?
Заголовок раздела «Q23: Что выведет?»var x interface{} = 5y, ok := x.(int)fmt.Println(y, ok)A: 5 true. Type assertion с , ok form — безопасный. Без ok, при ошибочном типе — panic.
Q24: Объясните, почему сравнение float опасно.
Заголовок раздела «Q24: Объясните, почему сравнение float опасно.»A: Float — приближение десятичных чисел в двоичной системе. 0.1 + 0.2 != 0.3. Сравнение через == непредсказуемо. Используется epsilon: math.Abs(a-b) < 1e-9.
Q25: Какие zero values у каждого composite типа?
Заголовок раздела «Q25: Какие zero values у каждого composite типа?»A:
- array
[N]T— все элементы zero (по типу T). - struct — все поля zero.
- slice —
nil(ноlen(s) == 0, можно append). - map —
nil(читать можно, писать → panic). - chan —
nil(read/write блокирует навсегда → deadlock). - func —
nil(вызов → panic). - interface —
nil(вызов метода → panic). - pointer —
nil.
6. Practice — задачки на проверку
Заголовок раздела «6. Practice — задачки на проверку»Задача 1: Найти zero value
Заголовок раздела «Задача 1: Найти zero value»type Config struct { Timeout time.Duration Hosts []string Cache map[string]int OnFatal func(error)}
var c Config// Что вернёт каждое из выражений?fmt.Println(c.Timeout) // ?fmt.Println(c.Hosts == nil) // ?fmt.Println(c.Cache == nil) // ?fmt.Println(c.OnFatal == nil) // ?Решение:
c.Timeout→0s(Duration — int64, zero = 0)c.Hosts == nil→truec.Cache == nil→truec.OnFatal == nil→true
Задача 2: iota magic
Заголовок раздела «Задача 2: iota magic»const ( _ = iota // ? A // ? B = iota * 10 // ? C // ? D = "x" // ? E // ? (внимание!))Решение:
_= 0A= 1 (повторениеiota)B= 20 (iota=2, expression iota*10)C= 30 (iota=3, повторение выражения)D= “x” (iota=4, но не используется)E= “x” (iota=5, повторение литерала"x")
Объяснение: повторяется полное выражение предыдущей строки. Иначе ошибка.
Задача 3: nil interface
Заголовок раздела «Задача 3: nil interface»type Writer interface { Write([]byte) (int, error)}
type NopWriter struct{}func (NopWriter) Write(b []byte) (int, error) { return len(b), nil }
func getWriter(useNop bool) Writer { var nw *NopWriter if useNop { nw = &NopWriter{} } return nw}
w := getWriter(false)fmt.Println(w == nil) // ?Решение: false. getWriter возвращает интерфейс с (type=*NopWriter, value=nil). Сам интерфейс — не nil.
✅ Правильный способ:
func getWriter(useNop bool) Writer { if !useNop { return nil } return &NopWriter{}}Задача 4: type alias trick
Заголовок раздела «Задача 4: type alias trick»type MyInt inttype MyIntAlias = int
func plusOne(x int) int { return x + 1 }
var a MyInt = 5var b MyIntAlias = 5
// plusOne(a) // ?// plusOne(b) // ?Решение:
plusOne(a)— ошибка,MyInt— отдельный тип.plusOne(b)— OK,MyIntAliasэтоint.
Задача 5: overflow trap
Заголовок раздела «Задача 5: overflow trap»var x int8 = 100y := x * xfmt.Println(y) // ?Решение: Результат int8 * int8 = int8. 100*100 = 10000, не влезает в int8 (max 127). Wrap around: 10000 % 256 = 16, со знаком — может быть отрицательное. Реально: 10000 - 39*256 = 16, но т.к. знаковое, 16 — итог.
Чтобы избежать: int(x) * int(x) = 10000.
7. Источники и дополнительно
Заголовок раздела «7. Источники и дополнительно»- The Go Programming Language Specification — https://go.dev/ref/spec (раздел Types, Constants, Conversions).
- Go Blog — Constants (Rob Pike) — https://go.dev/blog/constants — фундаментальное объяснение untyped constants.
- Dave Cheney — Go internals — https://dave.cheney.net/category/golang — серия постов про размеры, layout, escape analysis.
- Habr — “Скрытые особенности типов в Go” (2024-2025) — поищите свежие обзоры.
- Effective Go — https://go.dev/doc/effective_go — раздел про объявления и nil.
go vet— встроенный линтер ловитstring(int)без rune-конверсии,printfнесоответствия и др.fieldalignment—golang.org/x/tools/go/analysis/passes/fieldalignment— оптимизация порядка полей.- Russ Cox — Go Data Structures — https://research.swtch.com/godata — низкоуровневое представление типов.
Знание этого файла отличает джуна, который “слышал про Go” от того, кто понимает фундамент.