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 ., происходит следующее:
- Анализ и компиляция: Go тулчейн компилирует исходный код во временный бинарный файл. Это полноценная компиляция со всеми фазами оптимизации SSA.
- Временное хранилище: Бинарник создается во временной директории операционной системы (обычно
/tmpв Linux/macOS или%TEMP%в Windows). - Исполнение: Запускается дочерний процесс вашего скомпилированного бинарника (через системный вызов
exec/fork+exec), при этом драйверgoтранслирует в него потокиstdin,stdout,stderrи системные сигналы (SIGINT,SIGTERM). - Очистка: После завершения работы программы (или если она была прервана сигналом), временный файл и каталог сборки автоматически удаляются.
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:
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) строго до имени файла/точки, а аргументы и флаги вашего собственного приложения — после.
# Неправильно: go run попытается интерпретировать -config как свой флаг и завершится с ошибкой
go run main.go -config=dev.yaml
# Правильно: флаги go run идут до точки/файла, аргументы приложения — после
go run . -config=dev.yaml
Если имя передаваемого аргумента совпадает с флагом самого Go, можно использовать стандартный разделитель --:
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 конфигурации:
[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). |
Итог
go runне является интерпретатором, он компилирует код во временный файл и запускает его.- Всегда запускайте весь пакет (
go run .), а не один файл (go run main.go), чтобы избежать проблем с необъявленными символами. - Используйте флаг
-work, чтобы понять, куда Go складывает временные файлы и изучить промежуточный бинарник. - Для разработки серверов используйте инструменты Live Reload (Air), которые автоматизируют процесс перезапуска
go run.
Мы научились быстро запускать код локально. Но что, если нам нужно установить написанный инструмент в систему, чтобы вызывать его из любой точки терминала? Об этом поговорим в следующей статье: 5. go install и установка CLI.