Справочник

Язык 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
boolfalse
числовые типы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. Базовые типы и преобразования

ТипЧто хранитПримечание
booltrue или 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 valuenil

Пустой срез и 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 m0 итераций
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. Первоисточники