Язык Go — справочник
Пакеты и видимость, переменные и константы, zero values, базовые типы, строки и руны, массивы и срезы, мапы, структуры и методы, интерфейсы, дженерики, ошибки, defer, замыкания и циклы.
Собрано из спецификации языка, Effective Go и release notes Go 1.20–1.27. Версия языка — Go 1.27. Поведение, появившееся в конкретной версии, помечено: [Go 1.22], [Go 1.26] и так далее.
1. Пакеты, файлы, видимость
Каждый файл начинается с объявления пакета. Каталог — единица сборки: все файлы одного каталога принадлежат одному пакету. Импортный путь указывает на пакет, а имя пакета используется в селекторах: fmt.Println.
Идентификатор экспортируется, если выполнены оба условия: первый символ — заглавная буква Unicode (категория Lu), и идентификатор объявлен в блочном пространстве имён пакета, либо это имя поля или метода. Локальные переменные не экспортируются никогда.
| Идентификатор | Виден из другого пакета |
|---|---|
User, NewUser, (*User).Save | да |
user, newUser, save | нет |
_ | нигде: значение отбрасывается |
Формы импорта
| Запись | Что делает |
|---|---|
import "fmt" | пакет доступен как fmt |
import f "fmt" | псевдоним f |
import . "fmt" | имена пакета попадают в текущую область видимости |
import _ "net/http/pprof" | импорт только ради побочного эффекта: выполнить init |
Инициализация пакета
Сначала инициализируются переменные уровня пакета — в порядке зависимостей, затем выполняются функции init. Спецификация рекомендует сборщикам передавать файлы пакета в лексикографическом порядке имён, чтобы порядок был воспроизводим. В одном файле init выполняются в порядке объявления; функций init может быть несколько. Вызвать init явно нельзя.
Правило каталога internal задаёт go-команда, а не спецификация языка: пакет внутри internal можно импортировать только из поддерева, корень которого — родитель этого каталога.
package store
import (
"errors"
"fmt"
)
// User экспортирован: имя начинается с заглавной буквы.
type User struct {
ID int
name string // не экспортировано: видно только внутри пакета
}
func NewUser(id int, name string) (*User, error) {
if name == "" {
return nil, errors.New("store: пустое имя")
}
return &User{ID: id, name: name}, nil
}
// Rename экспортирован вместе с получателем *User.
func (u *User) Rename(name string) error {
if name == "" {
return fmt.Errorf("store: переименование %d: пустое имя", u.ID)
}
u.name = name
return nil
}
2. Переменные, константы, zero value
var объявляет переменную, := — короткое объявление внутри функции. В коротком объявлении хотя бы одна переменная слева должна быть новой; остальные могут быть переобъявлены, если они объявлены в том же блоке и имеют тот же тип.
Если явного начального значения нет, переменная или элемент получает zero value своего типа. Инициализация рекурсивна: у массива структур обнуляется каждое поле каждой структуры.
| Тип | Zero value |
|---|---|
bool | false |
| числовые типы | 0 |
string | "" |
| указатель, функция, интерфейс, срез, канал, мапа | nil |
| массив | все элементы — zero value своего типа |
| структура | все поля — zero value своих типов |
Константы и iota
Константы бывают типизированные и нетипизированные. Нетипизированная константа хранит точное значение произвольной точности; ошибка возникает только при попытке представить это значение в конкретном типе. В списке констант пропущенное выражение повторяет предыдущее, а iota увеличивается на каждом ConstSpec.
type Weekday int
const (
Sunday Weekday = iota // 0
Monday // 1: выражение повторено, iota = 1
Tuesday // 2
)
const Big = 1 << 100 // нетипизированная константа: точное значение
var x int64 = Big // ошибка компиляции: не помещается в int64
var y = Big // тоже ошибка: у нетипизированной слишком большое значение
new и make
new(T) выделяет переменную типа T с zero value и возвращает *T. С Go 1.26 аргументом может быть выражение: new(x) выделяет переменную типа x со значением x. make работает только со срезами, мапами и каналами и возвращает значение, а не указатель.
p := new(int) // *int, *p == 0
q := new(42) // *int, *q == 42 [Go 1.26]
s := make([]int, 0, 8) // len 0, cap 8
m := make(map[string]int)
ch := make(chan int, 4)
3. Базовые типы и преобразования
| Тип | Что хранит | Примечание |
|---|---|---|
bool | true или false | |
string | последовательность байтов | неизменяемая |
int, uint | целые | 32 или 64 бита, зависит от платформы |
int8, int16, int32, int64 | знаковые целые | 8, 16, 32, 64 бита |
uint8, uint16, uint32, uint64 | беззнаковые целые | 8, 16, 32, 64 бита |
uintptr | биты указателя | для низкоуровневых операций |
float32, float64 | числа с плавающей точкой | IEEE 754 |
complex64, complex128 | комплексные числа | |
byte | псевдоним uint8 | не отдельный тип |
rune | псевдоним int32 | кодовая точка Unicode |
any | псевдоним interface{} | с Go 1.18 |
error | интерфейс с методом Error() string |
Неявных преобразований между числовыми типами нет: int32 и int64 — разные типы, и присваивание требует явного T(x). Преобразование константы проверяется при компиляции; преобразование переменной выполняется во время работы, лишние биты отбрасываются. Для дробных в целое дробная часть отбрасывается; если значение не представимо в целевом типе, результат зависит от реализации.
var f float64 = 3.9
n := int(f) // 3: усечение к нулю
var i = 300
b := uint8(i) // 44: 300 по модулю 256
const c = 300
// var d uint8 = uint8(c) // ошибка компиляции: константа не помещается
s := string(rune(65)) // "A"; string(65) компилируется, но vet предупреждает
min, max, clear
| Вызов | Результат |
|---|---|
min(x, y, …) | наименьший из аргументов упорядоченного типа; если есть NaN, результат NaN |
max(x, y, …) | наибольший из аргументов упорядоченного типа |
clear(m) | удаляет все записи мапы; для nil-мапы — no-op |
clear(s) | обнуляет элементы среза до его длины; длина не меняется |
Аргументы min и max — фиксированное число значений, не срез: min(s...) не компилируется. Все три встроенные функции появились в Go 1.21.
4. Строки, байты, руны
Строка — неизменяемая последовательность байтов. len(s) возвращает число байтов, s[i] — байт с индексом i; взять адрес байта строки нельзя. Строки сравниваются лексикографически, побайтово.
| Операция | Единица |
|---|---|
len(s) | байты |
s[i] | byte |
for i, r := range s | индекс первого байта кодовой точки и rune |
utf8.RuneCountInString(s) | руны |
[]byte(s), string(b) | копия байтов |
Литералы: "..." — интерпретируемая строка с escape-последовательностями, `...` — сырая, без escape и с переводами строк, 'x' — литерал руны. При обходе строки через range некорректная последовательность UTF-8 даёт U+FFFD, а следующая итерация сдвигается на один байт.
s := "Привет"
fmt.Println(len(s)) // 12: байты
fmt.Println(utf8.RuneCountInString(s)) // 6: руны
for i, r := range s {
fmt.Println(i, r, string(r)) // i — индекс байта, r — rune
}
b := []byte(s) // копия
t := string(b) // ещё одна копия
Конкатенация в цикле создаёт новую строку на каждой итерации. Для сборки из частей есть strings.Builder и strings.Join; Builder не копирует данные при каждой записи.
var b strings.Builder
for i := 0; i < 3; i++ {
fmt.Fprintf(&b, "%d,", i)
}
s := b.String() // "0,1,2,"
5. Массивы и срезы
Массив — значение: длина входит в тип, присваивание копирует все элементы. Срез — дескриптор непрерывного сегмента массива: указатель на элементы, длина и ёмкость. Присваивание среза копирует дескриптор, поэтому два среза могут делить один массив.
| Свойство | Массив [N]T | Срез []T |
|---|---|---|
| Длина в типе | да | нет |
| Присваивание | копирует элементы | копирует дескриптор, массив общий |
Сравнение == | если элемент сравним | только с nil |
len и cap | равны N | могут различаться |
| Zero value | массив из zero value | nil |
Пустой срез и nil-срез ведут себя по-разному только там, где важно отличие «нет данных» от «пустой список»: append работает с обоими, len у обоих 0, а encoding/json кодирует nil как null, пустой — как [].
Нарезка
Выражение a[low:high] даёт срез от low до high-1; low по умолчанию 0, high — len(a). Трёхиндексная форма a[low:high:max] дополнительно ограничивает ёмкость: cap результата равен max-low.
a := []int{1, 2, 3, 4}
b := a[1:3] // len 2, cap 3
b = append(b, 99) // ёмкости хватает: пишет в общий массив
fmt.Println(a) // [1 2 3 99]
c := a[1:3:3] // cap 2: append выделит новый массив
append возвращает срез: если ёмкости хватает, он пишет в существующий массив, иначе выделяет новый и копирует элементы. Поэтому результат append может разделять память с исходным срезом. copy копирует элементы и возвращает их число.
src := []int{1, 2, 3}
dst := make([]int, len(src))
n := copy(dst, src) // n == 3
// удаление элемента с сохранением порядка
src = append(src[:1], src[2:]...) // [1 3]
Пакет slices (Go 1.21) даёт Clone, Equal, Contains, Index, Sort, SortFunc, Compact, Delete, Insert. С Go 1.23 там же итераторы: All, Values, Backward, Collect, Chunk.
6. Мапы
Мапа — неупорядоченный набор пар «ключ → значение». Тип ключа должен быть сравним: функции, мапы и срезы ключами быть не могут. Если ключ — интерфейс, сравнение выполняется по динамическому типу и значению; несравнимый динамический тип даёт панику во время работы.
| Операция | nil-мапа |
|---|---|
m[k] | zero value типа значения |
len(m) | 0 |
delete(m, k) | no-op |
clear(m) | no-op |
for k := range m | 0 итераций |
m[k] = v | паника: assignment to entry in nil map |
Порядок обхода мапы не определён и не гарантирован от одной итерации к другой. Если во время обхода удалить ещё не пройденную запись, она не будет выдана; если добавить запись, она может быть выдана или пропущена.
m := map[string]int{"a": 1}
v, ok := m["b"] // v == 0, ok == false
if !ok {
m["b"] = 0
}
delete(m, "a")
clear(m) // мапа пуста, но не nil
// элемент мапы не адресуем:
// m["a"].Field = 1 // не компилируется
Мапа не безопасна для одновременной записи из нескольких горутин; для этого нужен мьютекс или sync.Map. Пакет maps (Go 1.21) даёт Clone, Copy, Equal, а с Go 1.23 — итераторы All, Keys, Values, Insert, Collect.
7. Структуры, методы, embedding
Литерал структуры бывает позиционный и с ключами. Позиционный требует все поля в порядке объявления; с ключами можно опустить часть полей — они получат zero value. Неэкспортированные поля чужого пакета в литерале задать нельзя.
Теги — строковые метаданные поля; их читает reflect, например encoding/json. С Go 1.27 ключом в литерале может быть любой допустимый селектор поля, а не только имя верхнего уровня.
type Point struct {
X, Y int
}
p1 := Point{1, 2}
p2 := Point{X: 1} // Y == 0
type User struct {
ID int `json:"id"`
Name string `json:"name,omitempty"`
}
Получатели и method set
Метод объявляется с получателем-значением или получателем-указателем. Method set определяет, какие методы доступны через значение типа и через указатель.
| Объявление | В method set T | В method set *T |
|---|---|---|
func (t T) M() | да | да |
func (t *T) M() | нет | да |
Вызов t.M() для метода с получателем-указателем компилируется, если t адресуем: компилятор подставляет (&t).M(). Элемент мапы не адресуем, поэтому m[k].M() для такого метода не компилируется.
type Counter struct{ n int }
func (c Counter) Value() int { return c.n }
func (c *Counter) Inc() { c.n++ }
var c Counter
c.Inc() // (&c).Inc(): c.n == 1
fmt.Println(c.Value())
Встраивание
Встроенное поле объявляется без имени — только тип; именем поля становится имя типа. Поля и методы встроенного типа продвигаются: доступны через внешнюю структуру, если не перекрыты полем или методом на меньшей глубине. Два встроенных типа на одной глубине с одинаковым именем дают неоднозначный селектор.
type Logger struct{ prefix string }
func (l Logger) Log(msg string) { fmt.Println(l.prefix, msg) }
type Service struct {
Logger // поле называется Logger
Name string
}
s := Service{Logger: Logger{prefix: "svc"}, Name: "billing"}
s.Log("start") // продвинутый метод
Method set структуры S с встроенным T включает продвинутые методы T; method set *S — продвинутые методы и T, и *T.
8. Интерфейсы
Интерфейс задаёт набор типов. Тип реализует интерфейс неявно: достаточно, чтобы его method set содержал все методы интерфейса. Объявлять «implements» не нужно. Пустой интерфейс interface{}, он же any, реализуют все типы.
Интерфейс с элементами типа — объединениями (int | string) или ~T — можно использовать только как ограничение type parameter, но не как тип переменной.
nil-интерфейс и типизированный nil
Значение интерфейса равно nil, только если равны и динамический тип, и динамическое значение. Указатель, положенный в интерфейс, сохраняет динамический тип, поэтому такой интерфейс не равен nil.
type MyError struct{}
func (e *MyError) Error() string { return "my error" }
func do() error {
var e *MyError = nil
return e // err != nil: динамический тип *MyError, значение nil
}
Type assertion и type switch
| Форма | Поведение |
|---|---|
v := x.(T) | паника, если динамический тип не T |
v, ok := x.(T) | при неудаче v — zero value T, ok == false |
switch v := x.(type) | ветка по динамическому типу; fallthrough запрещён |
switch v := x.(type) {
case nil:
fmt.Println("nil")
case int:
fmt.Println("int", v)
case error:
fmt.Println("error", v)
default:
fmt.Printf("%T\n", v)
}
Интерфейсы встраиваются друг в друга: io.ReadWriter — это io.Reader плюс io.Writer. Проверка соответствия на этапе компиляции записывается пустым объявлением:
var _ io.Reader = (*bytes.Buffer)(nil)
9. Дженерики
Функция, тип или метод может объявить type parameters в квадратных скобках. Каждый параметр имеет ограничение — constraint, то есть интерфейс, задающий допустимые типы и доступные операции.
| Constraint | Что допускает |
|---|---|
any | любой тип |
comparable | типы, для которых определены == и != |
~int | типы с underlying type int, включая type ID int |
int | string | объединение типов |
interface{ M() } | типы с методом M |
type Number interface {
~int | ~int64 | ~float64
}
func Sum[T Number](xs []T) T {
var total T
for _, x := range xs {
total += x // + доступен: он есть у всех типов из constraint
}
return total
}
type ID int64
total := Sum([]ID{1, 2, 3}) // ID подходит через ~int64
Тип-аргумент обычно выводится из аргументов функции. Если вывести не удаётся, его указывают явно: Sum[ID](xs). Дженерик-типы инстанцируются так же: Pair[string, int].
Версии: generic type aliases полностью поддержаны с Go 1.24 (type Set[T comparable] = map[T]struct{}); generic methods — с Go 1.27. Методы интерфейсов не могут объявлять type parameters, и generic-метод не может реализовать метод интерфейса. Отдельной специализации под каждый тип, как в C++, нет.
type Pair[K comparable, V any] struct {
Key K
Value V
}
// generic method [Go 1.27]
func (p Pair[K, V]) Map[T any](f func(V) T) Pair[K, T] {
return Pair[K, T]{Key: p.Key, Value: f(p.Value)}
}
10. Ошибки, panic, recover
error — интерфейс с единственным методом Error() string. Ошибка может быть sentinel-значением, собственным типом или обёрткой над другой ошибкой.
| Средство | Что делает |
|---|---|
errors.New | создаёт ошибку с фиксированным текстом |
fmt.Errorf("%w", err) | оборачивает одну ошибку; Unwrap возвращает её |
fmt.Errorf("%w … %w", e1, e2) | оборачивает несколько ошибок (Go 1.20) |
errors.Is(err, target) | сравнивает с целевой ошибкой, разворачивая цепочку |
errors.As(err, &target) | ищет в цепочке ошибку нужного конкретного типа |
errors.Join(errs...) | объединяет ошибки; Unwrap() []error; nil, если все nil |
var ErrNotFound = errors.New("not found")
func Load(id int) error {
if id <= 0 {
return fmt.Errorf("load %d: %w", id, ErrNotFound)
}
return nil
}
err := Load(0)
fmt.Println(errors.Is(err, ErrNotFound)) // true
type ValidationError struct{ Field string }
func (e *ValidationError) Error() string { return "invalid " + e.Field }
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println(ve.Field)
}
panic прекращает обычное выполнение функции и запускает её отложенные вызовы. recover возвращает значение паники, но работает только внутри отложенной функции; вне паники он возвращает nil. С Go 1.21 panic(nil) даёт панику типа *runtime.PanicNilError, поэтому recover не может вернуть nil незаметно.
Соглашение: error — для ожидаемых ошибок, которые вызывающий может обработать; panic — для программных дефектов и нарушенных инвариантов. os.Exit завершает программу сразу и отложенные вызовы не выполняет.
11. defer
defer откладывает вызов функции до возврата из окружающей функции — по return, по концу тела или при панике. Аргументы отложенного вызова вычисляются в момент defer, а сам вызов выполняется позже. Отложенные вызовы идут в обратном порядке: последний объявленный выполняется первым.
func f() (result int) {
defer func() { result *= 7 }()
return 6 // result = 6, затем defer: 42
}
for i := 0; i <= 3; i++ {
defer fmt.Print(i) // напечатает 3 2 1 0
}
Отложенная функция может изменить именованный результат: return сначала присваивает результат, затем выполняются defer, затем функция возвращается. Возвращаемые значения самой отложенной функции отбрасываются.
В цикле defer накапливается до выхода из функции: файлы закроются только в конце, а не на каждой итерации. Если ресурс нужно освобождать сразу, вызов закрытия делают явно или оборачивают итерацию в отдельную функцию.
for _, name := range names {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close() // все файлы закроются при выходе из функции
}
runtime.Goexit выполняет отложенные вызовы текущей горутины, os.Exit — нет.
12. Замыкания, циклы, switch
Функциональное значение может захватывать переменные из окружающей области; замыкание видит переменную, а не копию её значения. С Go 1.22 переменные, объявленные в for или в range, создаются заново на каждой итерации — поэтому замыкания больше не делят одну переменную.
var prints []func()
for i := 0; i < 5; i++ {
prints = append(prints, func() { fmt.Println(i) })
i++
}
for _, p := range prints {
p() // 1 3 5; до Go 1.22 было бы 6 6 6
}
Семантика определяется версией языка в go.mod: для модулей с go 1.21 и ниже сохраняется общая переменная цикла.
Что даёт range
| Выражение | Первое значение | Второе значение |
|---|---|---|
| массив, указатель на массив, срез | индекс int | элемент |
string | индекс первого байта | rune |
| мапа | ключ | значение |
| канал | принятое значение | — |
целое n [Go 1.22] | 0 … n-1 | — |
| функция-итератор [Go 1.23] | значения из yield | значения из yield |
for i := range 3 {
fmt.Println(i) // 0 1 2
}
outer:
for _, row := range grid {
for _, v := range row {
if v < 0 {
break outer // break с меткой выходит из обоих циклов
}
}
}
switch не проваливается в следующую ветку по умолчанию: fallthrough нужно писать явно, и он допустим только в выражении-переключателе, но не в type switch. В switch можно объявить переменную перед условием: switch x := f(); x { … }.
switch n := len(s); {
case n == 0:
fmt.Println("пусто")
case n < 10:
fmt.Println("короткая")
default:
fmt.Println("длинная")
}
13. Первоисточники
- The Go Programming Language Specification — объявления, типы, операторы, инструкции, пакеты
- Effective Go — идиомы: получатели, встраивание, интерфейсы, ошибки, panic и recover
- Go FAQ — срезы, мапы, интерфейсы, сборка мусора
- Slices: usage and internals — устройство дескриптора среза и append
- Fixing for loops in Go 1.22 — переменные цикла
- Release notes Go 1.20 — несколько
%w,errors.Join - Release notes Go 1.21 —
min,max,clear,slices,maps,panic(nil) - Release notes Go 1.22 — per-iteration переменные цикла,
rangeпо целому - Release notes Go 1.23 —
rangeпо функции, итераторы вslicesиmaps - Release notes Go 1.24 — generic type aliases
- Release notes Go 1.26 —
new(expr), самоссылающиеся constraints - Release notes Go 1.27 — generic methods, селектор поля как ключ литерала
- pkg.go.dev/errors —
Is,As,Join,Unwrap - pkg.go.dev/slices и pkg.go.dev/maps — обобщённые операции над срезами и мапами