Оглавление

  1. История и философия языка
  2. Go Runtime и сборка мусора (GC)
  3. Настройка окружения и CLI
  4. Типы данных и переменные
  5. Управляющие конструкции
  6. Коллекции: Массивы и Слайсы
  7. Коллекции: Maps (Хеш-таблицы)
  8. Функции
  9. Структуры, Методы и Указатели
  10. Интерфейсы
  11. Обработка ошибок
  12. Экосистема, HTTP и организация проекта

1. История и философия языка

  • Происхождение: Разработан в Google (Роберт Гризмер, Кен Томпсон, Роб Пайк). Выпущен в Open Source в 2009 году.
  • Цели создания:
    • Ускорение сборки и разработки крупных проектов.
    • Упрощение управления зависимостями.
    • Повышение читаемости кода.
    • Создание удобных инструментов для конкурентного программирования.
  • Ключевые принципы:
    • Минимализм: Простой синтаксис, отсутствие лишних конструкций.
    • Единообразие: Стандарт форматирования gofmt закреплен на уровне языка.
    • Быстрая компиляция: Пакетная система позволяет компилировать код параллельно.
    • Явное лучше неявного: Отсутствие неявных преобразований типов и скрытых исключений.

2. Go Runtime и сборка мусора (GC)

Go Runtime — это код, исполняемый в фоне вместе с приложением. Он отвечает за инициализацию памяти, планировщик горутин и работу GC.

Сборщик мусора (GC)

  • Алгоритм: Трассирующий конкурентный сборщик мусора на основе Concurrent Mark and Sweep (пометка и очистка).
  • Фазы работы:
    1. Mark Setup (STW): Короткая пауза для включения барьера записи.
    2. Concurrent Mark: Поиск “живых” объектов параллельно с работой кода.
    3. Mark Termination (STW): Завершение пометки.
    4. Concurrent Sweep: Освобождение памяти от “мертвых” объектов.
  • Особенности:
    • Цель по STW-паузам — менее 1 мс.
    • Барьеры записи (Write Barriers): Позволяют GC отслеживать изменения ссылок во время работы приложения.
    • Настройка GOGC: Переменная определяет частоту запуска (дефолт 100 — запуск при удвоении кучи).

3. Настройка окружения и CLI

Переменные окружения

  • GOPATH: Устаревшее рабочее пространство, сейчас используется для кеша модулей и бинарных файлов.
  • GO111MODULE: Режим работы с модулями (по умолчанию on).
  • GOPROXY: Прокси для скачивания зависимостей.
  • GOPRIVATE: Список репозиториев, которые не нужно проверять через публичные прокси.

Основные команды CLI

  • go mod init <name>: Инициализация нового модуля.
  • go mod tidy: Синхронизация зависимостей (удаляет лишние, скачивает нужные).
  • go run <file>: Компиляция и мгновенный запуск.
  • go build: Сборка исполняемого бинарного файла.
  • go env: Просмотр настроек окружения.

4. Типы данных и переменные

Примитивные типы

  • Нулевые значения (Zero Values):
    • int, float, byte, rune: 0
    • bool: false
    • string: ""
    • pointer, slice, map, chan, interface, func: nil
  • Числовые типы: Строго разграничены (нельзя сложить int32 и int64 без явного приведения).
  • Преобразование типов: Всегда явное — int64(myInt32).

Строки, байты и руны

  • String: Неизменяемая последовательность байт. Длина len() возвращает количество байт, а не символов.
  • Byte: Алиас для uint8.
  • Rune: Алиас для int32, представляет один символ Unicode.
  • Итерация: for i := range str идет по байтам, for i, r := range str идет по рунам.

Константы и iota

  • const: Значения вычисляются на этапе компиляции.
  • iota: Целочисленный итератор внутри блока const. Сбрасывается в 0 в начале блока и увеличивается на 1 в каждой строке.

5. Управляющие конструкции

  • if-else: Поддерживает короткое объявление переменной в условии: if err := call(); err != nil { ... }. Область видимости переменной ограничена блоком if-else.
  • for: Единственный вид цикла в Go.
    • Классический: for i := 0; i < 10; i++.
    • Условный (как while): for condition { }.
    • Бесконечный: for { }.
    • Range: for k, v := range collection.
  • switch-case:
    • Неявный break: Выполнение не проваливается в следующий case автоматически.
    • fallthrough: Ключевое слово для явного проваливания в следующий блок (без проверки условия).

6. Коллекции: Массивы и Слайсы

Массивы

  • Фиксированная длина: Размер — часть типа ([5]int и [10]int — разные типы).
  • Передача: Всегда копируются по значению.

Слайсы

  • Структура: Слайс — это дескриптор, содержащий:
    1. Pointer: Указатель на базовый массив.
    2. Len: Текущая длина.
    3. Cap: Емкость (размер базового массива).
  • Динамика: При превышении cap, append создает новый массив (обычно удвоенного размера) и копирует данные.
  • Копирование: Присваивание слайса (s2 := s1) копирует только дескриптор. Оба слайса будут указывать на один базовый массив (Shallow Copy).
  • Slicing: Выражение s[low:high] создает слайс, разделяющий память с оригиналом.

7. Коллекции: Maps (Хеш-таблицы)

  • Структура hmap: Заголовок содержит count, B (логарифм кол-ва бакетов), hashseed и указатель на buckets.
  • Бакеты (bmap): Каждый бакет хранит до 8 пар ключ-значение. Внутри бакета сначала идут tophash (8 старших бит хэша), затем 8 ключей, затем 8 значений.
  • Особенности:
    • Небезопасны для конкурентного использования.
    • Случайный порядок обхода: Go специально рандомизирует итерацию.
    • Load Factor: При достижении средней нагрузки в 6.5 элементов на бакет начинается эвакуация (рост карты в 2 раза). Эвакуация происходит постепенно.
    • Указатели: Нельзя взять адрес элемента карты (&m[k]), так как при росте карты элементы перемещаются в памяти.

8. Функции

Основы и Variadic-функции

  • Множественный возврат: Стандартный паттерн — (value, error).
  • Variadic (...T): Принимает любое количество аргументов, внутри функции они доступны как слайс.

Анонимные функции и замыкания

  • Функции — “first class citizens”: их можно передавать, возвращать и присваивать.
  • Замыкание (Closure): Анонимная функция, “захватывающая” переменные из своего лексического окружения.

Инициализация глобальных переменных с вызовом функций

  • В Go глобальные (package-level) переменные инициализируются до вызова main().
  • Если переменная определяется через немедленно вызываемую функцию (IIFE — Immediately Invoked Function Expression), то эта функция выполнится при старте программы:
var a = func() int {
    fmt.Println("5") // выполнится сразу при старте
    defer func() {
        fmt.Println("2") // выполнится после return
    }()
    return 0
}()
Порядок выполнения:
  1. Инициализация переменной a:
    • Создаётся анонимная функция.
    • Немедленно вызывается ().
  2. Выполняется fmt.Println("5").
  3. Регистрируется defer.
  4. Возвращается значение 0.
  5. Выполняется defer, выводящий 2.

Вывод программы при старте:

5
2
Важные моменты:
  • Такой код выполняется до main(), даже если main() пустой.
  • Если убрать () после функции, она не будет вызвана при старте, а станет обычной анонимной функцией, которую можно вызвать позже:
var a = func() int {
    fmt.Println("5")
    return 0
} // без ()

Оператор defer

  • Откладывает выполнение функции до момента выхода из текущей функции.
  • LIFO: Выполняются в обратном порядке объявления.
  • Аргументы: Вычисляются в момент объявления defer, а не в момент выполнения.

9. Структуры, Методы и Указатели

Указатели

  • Хранят адрес в памяти. Нулевое значение — nil.
  • Разыменование nil-указателя вызывает панику.
  • Операторы: & (взятие адреса), * (разыменование).

Структуры и встраивание

  • Видимость: Поля с большой буквы — публичные, с маленькой — приватные.
  • Встраивание (Embedding): Позволяет встроить структуру без имени поля, “продвигая” её методы и поля на верхний уровень (замена наследованию).

Методы и ресиверы

  • Value Receiver (func (s T) Method()): Работает с копией. Не может изменить состояние оригинала.
  • Pointer Receiver (func (s *T) Method()): Работает с оригиналом через указатель. Позволяет менять поля структуры.
  • Соглашение: Все методы одного типа должны иметь либо только value, либо только pointer ресиверы.

10. Интерфейсы

  • Неявная реализация: Тип реализует интерфейс, если у него есть все методы интерфейса (без ключевых слов вроде implements).
  • Empty Interface (any): Удовлетворяет любому типу.
  • Правило: “Принимайте интерфейсы, возвращайте структуры”.
  • Интерфейс под капотом — это пара (type, value). Если value == nil, но type != nil, сам интерфейс не равен nil.

11. Обработка ошибок

  • error — это интерфейс с методом Error() string.
  • Идиома: Проверка ошибки сразу после вызова — if err != nil { return err }.
  • Wrapping: Оборачивание ошибки с помощью fmt.Errorf("...: %w", err).
  • Проверка:
    • errors.Is(err, target): Проверка на равенство конкретной ошибке в цепочке.
    • errors.As(err, &target): Проверка на принадлежность типу в цепочке.

12. Экосистема, HTTP и организация проекта

  • Проект:
    • /cmd: Точки входа (main.go).
    • /internal: Приватный код проекта.
    • /pkg: Публичные библиотеки (использовать осторожно).
  • HTTP:
    • Клиент: Нужно всегда закрывать тело ответа res.Body.Close().
    • Middleware: Оборачивание http.Handler для добавления логики (логи, авторизация).
  • Context: Стандартный способ управления таймаутами, отменой задач и передачи метаданных (trace ID). Передается первым аргументом.