4. go run и быстрый цикл разработки

Разработчики, приходящие из Python, PHP или Node.js, привыкли к быстрому интерактивному циклу: изменил строку кода -> нажал Enter -> моментально увидел результат в консоли. В классических компилируемых языках (C, C++, Rust) этот цикл традиционно ощутимо длиннее: изменил код -> запустил сборщик -> подождал компиляцию всех модулей и линковку -> запустил бинарник -> увидел результат.

Команда go run — это инженерный мост между этими мирами. Она создает полную иллюзию интерпретации, хотя под капотом происходит бескомпромиссная компиляция в машинный код. Скорость компилятора Go настолько высока, что запуск программы ощущается мгновенным. Однако понимание того, как устроена go run, позволяет использовать её эффективно и не попадать в коварные ловушки накладных расходов и утечек дискового пространства.


Как работает go run (Under the hood)

Многие начинающие программисты ошибочно полагают, что go run — это специальный интерпретатор или виртуальная машина. Это опасное заблуждение. go run — это автоматизированный сценарий компиляции, линковки и запуска процесса.

Когда вы запускаете go run main.go или go run ., происходит следующее:

  1. Анализ и компиляция: Go тулчейн компилирует исходный код во временный бинарный файл. Это полноценная компиляция со всеми фазами оптимизации SSA.
  2. Временное хранилище: Бинарник создается во временной директории операционной системы (обычно /tmp в Linux/macOS или %TEMP% в Windows).
  3. Исполнение: Запускается дочерний процесс вашего скомпилированного бинарника (через системный вызов exec / fork+exec), при этом драйвер go транслирует в него потоки stdin, stdout, stderr и системные сигналы (SIGINT, SIGTERM).
  4. Очистка: После завершения работы программы (или если она была прервана сигналом), временный файл и каталог сборки автоматически удаляются.
flowchart LR
    classDef process  fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
    classDef success  fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
    classDef error    fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
    classDef warning  fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
    classDef entry    fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
    classDef aux      fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
    classDef system   fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
    classDef data     fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
    classDef network  fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
    classDef accent   fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
    A["go run main.go"]:::entry --> B["Компиляция в tmp"]
    B --> C["Создание временного бинарника"]
    C --> D["exec - запуск процесса"]
    D --> E["Работа программы"]
    E --> F["Выход и удаление файла"]
  

Разница между файлом и пакетом

Это классическая ошибка разработчиков любого уровня:

  • go run main.go — компилирует и запускает только этот единственный файл. Если в вашей директории рядом лежат другие файлы того же пакета package main с функциями, типами или переменными, которые вызывает main.go, вы получите ошибку компиляции undefined.
  • go run . — компилирует и запускает весь пакет в текущей директории со всеми его файлами. Это единственный правильный и идиоматичный способ запуска локальных программ.

Также можно запускать пакеты по полному пути импорта, если они находятся в GOPATH или объявлены в go.mod:

Bash
go run github.com/myuser/myproject/cmd/server

Начиная с Go 1.16, вы можете запускать даже удаленные утилиты без предварительного клонирования репозитория, указав версию модуля: go run golang.org/x/tools/cmd/stringer@latest.


Аргументы и флаги

Частая путаница возникает при передаче флагов приложению. go run принимает свои собственные флаги (например, -race, -work или -tags) строго до имени файла/точки, а аргументы и флаги вашего собственного приложения — после.

Bash
# Неправильно: go run попытается интерпретировать -config как свой флаг и завершится с ошибкой
go run main.go -config=dev.yaml

# Правильно: флаги go run идут до точки/файла, аргументы приложения — после
go run . -config=dev.yaml

Если имя передаваемого аргумента совпадает с флагом самого Go, можно использовать стандартный разделитель --:

Bash
go run . -- -tags myvalue

Инструментарий для разработки (Dev Tools)

go run отлично подходит для быстрых экспериментов, но для реальной разработки на бэкенде (HTTP-серверы, gRPC-сервисы) перезапускать сервер вручную после каждого изменения утомительно.

В экосистеме Go стандартом де-факто стали инструменты Live Reload. Они используют go run или go build под капотом, но автоматически перезапускают процесс при изменении файлов.

Самый популярный инструмент — Air (air-verse/air, ранее cosmtrek/air).

Пример .air.toml конфигурации:

TOML
[build]
  cmd = "go build -o ./tmp/main ."
  bin = "./tmp/main"
  include_ext = ["go", "tpl", "tmpl", "html"]
  exclude_dir = ["assets", "tmp", "vendor"]

Air следит за файловой системой (используя библиотеку fsnotify), при изменении .go файлов вызывает go build, убивает старый процесс и запускает новый.


go run vs go build: Что выбрать?

Характеристика go run go build
Результат Запуск программы, артефакт удаляется. Создание бинарника на диске.
Скорость старта Чуть медленнее (нужно скомпилировать). Мгновенный запуск готового файла.
Использование Локальная разработка, скрипты, прототипирование. CI/CD, продакшн, распространение.
Отладка Удобно для быстрых проверок. Требует сборки и запуска дебаггера (Delve).

Итог

  1. go run не является интерпретатором, он компилирует код во временный файл и запускает его.
  2. Всегда запускайте весь пакет (go run .), а не один файл (go run main.go), чтобы избежать проблем с необъявленными символами.
  3. Используйте флаг -work, чтобы понять, куда Go складывает временные файлы и изучить промежуточный бинарник.
  4. Для разработки серверов используйте инструменты Live Reload (Air), которые автоматизируют процесс перезапуска go run.

Мы научились быстро запускать код локально. Но что, если нам нужно установить написанный инструмент в систему, чтобы вызывать его из любой точки терминала? Об этом поговорим в следующей статье: 5. go install и установка CLI.