From 4e04af871c26c98bc20a5466d2061b82c7b9edd3 Mon Sep 17 00:00:00 2001 From: Alex B Date: Fri, 20 Feb 2026 00:34:36 +0200 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20add=20lesson=201.6=20=E2=80=94=20co?= =?UTF-8?q?mpilation=20and=20execution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + .idea/.gitignore | 8 - .idea/Godojo.iml | 9 - .idea/copilot.data.migration.agent.xml | 6 - .idea/modules.xml | 8 - .idea/vcs.xml | 7 - .../public/images/go-compilation-pipeline.png | Bin 0 -> 65513 bytes apps/web/src/content/docs/en/installation.md | 2 +- .../src/content/docs/ru/compile-and-run.md | 835 ++++++++++++++++++ .../src/content/docs/ru/program-structure.md | 5 +- apps/web/src/data/sidebar-unified.json | 4 + apps/web/src/data/sidebar.json | 4 + apps/web/src/data/toc.json | 5 + .../content/01-intro/03-installation.md | 2 +- .../content/01-intro/06-compile-and-run.md | 819 ++++++++++++++++- 15 files changed, 1669 insertions(+), 48 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/Godojo.iml delete mode 100644 .idea/copilot.data.migration.agent.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml create mode 100644 apps/web/public/images/go-compilation-pipeline.png create mode 100644 apps/web/src/content/docs/ru/compile-and-run.md diff --git a/.gitignore b/.gitignore index cbe5cd8..3d2ef79 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ dist/ .env .env.* !.env.example +research/**/*.md +tips.md +.idea/ diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/Godojo.iml b/.idea/Godojo.iml deleted file mode 100644 index 5e764c4..0000000 --- a/.idea/Godojo.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml deleted file mode 100644 index 4ea72a9..0000000 --- a/.idea/copilot.data.migration.agent.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index e539264..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 49ee348..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/apps/web/public/images/go-compilation-pipeline.png b/apps/web/public/images/go-compilation-pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..507bf4516a06f61d97592096a201474b24c92030 GIT binary patch literal 65513 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 подход, и позже вы поймёте почему. + +:::caution Точка работает только с Go-модулем +Команды с `.` (`go run .`, `go build .`) требуют файла `go.mod` в директории вашего проекта. Если его нет, Go выдаст ошибку. Создайте модуль одной командой: + +```bash +go mod init hello +``` + +`hello` — это имя вашего модуля (можно любое). После этого в директории появится файл `go.mod`. Подробнее про модули поговорим в отдельном уроке, а пока достаточно знать: **перед первым использованием `.` выполните `go mod init`**. +::: + +* **Идиоматичный** — значит "так принято в сообществе". У каждого языка есть негласные правила: не просто "работает", а "так пишут опытные разработчики". В Go-мире `go run .` — идиоматично, `go run main.go` — нет. Вы будете часто встречать это слово в Go-документации и статьях. + +### Ограничения + +С версии **Go 1.24** у `go run` появилось кэширование: если код не менялся, повторный запуск берёт готовый бинарник из кэша. Но ограничений всё равно хватает: + +- **Медленнее прямого запуска.** Даже с кэшем `go run` проверяет актуальность сборки при каждом вызове. Для крупных проектов накладные расходы могут быть значительными — в некоторых случаях до 8 раз медленнее, чем запуск готового бинарника. +- **Нет контроля над бинарником.** Он лежит где-то в кэше, вы не можете его передать или отправить на сервер. `os.Executable()` вернёт путь во временную директорию — если ваша программа полагается на свой путь, это сломается. +- **Кэш недолговечен.** Go агрессивно чистит кэшированные бинарники — примерно через 2 дня неиспользования. Утром `go run` может снова компилировать с нуля. +- **Только `package main`.** Нельзя запустить библиотечный пакет — только исполняемые программы. +- **Кросс-компиляция бессмысленна.** `GOOS=linux go run .` не имеет смысла — бинарник запускается на вашей машине, а не на целевой платформе. +- **Не для продакшена.** Продакшен (production) — это среда, где ваша программа работает "по-настоящему": обслуживает реальных пользователей, крутится на сервере 24/7. В противоположность — среда разработки (development), где вы пишете и тестируете код на своём компьютере. `go run` — инструмент разработки, не способ деплоя в продакшен. + +**Вывод:** `go run` — это "быстро глянуть". Для всего остального есть `go build`. + +--- + +## `go build` — создаём настоящий бинарник + +Когда нужен файл, который можно отправить коллеге, загрузить на сервер или положить в Docker-контейнер (Docker — система для упаковки и запуска приложений в изолированных средах, подробнее познакомимся позже) — используйте `go build`. + +```bash +go build -o myapp . +./myapp +Привет, мир! +``` + +В директории появился файл `myapp` (на Windows — `myapp.exe`) размером около 2 МБ или чуть больше. Это самодостаточный бинарник. На целевой машине не нужен Go, не нужны библиотеки, не нужен рантайм. Просто копируете файл и запускаете. + +### Имя выходного файла + +Без флага `-o` Go сам выберет имя: + +```bash +go build . # Имя из go.mod (или имя директории) +go build -o server . # Явно задаём имя +go build -o bin/app . # Можно указать и путь +``` + +На Windows автоматически добавится `.exe`. + +:::tip `go build` без `package main` +Если запустить `go build .` в директории с библиотечным пакетом (не `main`), Go скомпилирует код и проверит его на ошибки, но **не создаст выходного файла**. Удобный способ валидировать код без засорения директории — особенно в CI. +::: + +### Флаг `-race` — детектор гонок данных + +Представьте: два человека одновременно редактируют один документ, не видя друг друга. Один пишет заголовок, другой его удаляет — результат непредсказуем. В программировании это называется **гонка данных** (data race) — когда несколько частей программы одновременно читают и изменяют одну и ту же переменную. Результат зависит от того, кто успел первым, и каждый запуск может давать разный результат. + +Go умеет такие ситуации находить автоматически. Вот пример — не пытайтесь пока разобрать каждую строку, ключевое слово `go` мы изучим в уроке про конкурентность (Concurrency). Сейчас важен сам принцип: + +```go +package main + +import "fmt" + +func main() { + count := 0 + for i := 0; i < 1000; i++ { + go func() { // запускаем 1000 параллельных задач + count++ // все пишут в одну переменную + }() + } + fmt.Println(count) +} +``` + +Запустим без флага — программа молча выдаст непредсказуемый результат: + +```bash +go run . +0 # или 127, или 999 — каждый раз по-разному +``` + +Теперь с `-race`: + +```bash +go run -race main.go +================== +WARNING: DATA RACE +Read at 0x00c00011c028 by goroutine 9: + main.main.func1() + /home/user/main.go:9 +0x2e + +Previous write at 0x00c00011c028 by goroutine 8: + main.main.func1() + /home/user/main.go:9 +0x44 + +Goroutine 9 (running) created at: + main.main() + /home/user/main.go:8 +0x4a + +Goroutine 8 (finished) created at: + main.main() + /home/user/main.go:8 +0x4a +================== +... ещё 2 похожих предупреждения ... +651 +Found 3 data race(s) +exit status 66 +``` + +Go нашёл **3 гонки** и точно показывает: строка 9, переменная `count` (адрес `0x00c00011c028`), несколько параллельных задач пишут в неё одновременно. Программа завершилась с кодом 66 — специальный код ошибки для гонок. Число `651` вместо ожидаемых `1000` — результат потерянных обновлений, типичное последствие гонки данных. Без `-race` программа бы тихо выдавала неправильные результаты — баг, который крайне сложно поймать вручную. + +Флаг `-race` работает и с `go build`, и с `go run`, и с `go test`. Бинарник с ним медленнее и потребляет больше памяти, поэтому в продакшен его не ставят. Но в тестах и при разработке — обязательная практика. + +### Почему Hello World весит 2 мегабайта? + +После первого `go build` новички удивляются: программа из пяти строк — и 2 МБ? На C аналог весит 16 КБ. + +Дело в том, что Go-бинарник — это не просто ваш код. Это целая вселенная: + +- **Go runtime** — среда выполнения +- **Garbage Collector** — сборщик мусора +- **Goroutine Scheduler** — планировщик горутин (помните из первого урока — многозадачность из коробки?) +- **Таблица символов и отладочная информация** — для паник-трейсов и отладки +- Части стандартной библиотеки, которые вы импортировали + +Один только `import "fmt"` тянет за собой рефлексию*, I/O* и форматирование строк*. Всё встроено внутрь. + +- * **Рефлексия** (Reflection) — механизм, позволяющий программе анализировать и изменять собственную структуру во время выполнения: узнавать типы переменных, читать их значения, вызывать функции по имени. Проще говоря — способность программы "смотреть на саму себя". Вспомните: вы пишете `fmt.Println(42)` — и получаете `42`. Пишете `fmt.Println("привет")` — получаете `привет`. Пишете `fmt.Println(3.14)` — получаете `3.14`. Откуда `Println` знает, как напечатать каждое из этих значений, если число и строка — совершенно разные вещи? Именно через рефлексию: в момент вызова функция спрашивает у рантайма "что мне передали — число? строку? что-то ещё?" — и на основе ответа выбирает, как это отобразить. Без рефлексии пришлось бы писать отдельную функцию для каждого типа данных. +- * **I/O** (Input/Output, ввод/вывод) — всё что связано с чтением и записью: вывод в терминал, чтение файлов, передача данных по сети. `fmt.Println` записывает текст в стандартный поток вывода (stdout) — это I/O. +- * **Форматирование строк** — превращение данных в читаемый текст. Когда вы пишете `fmt.Println("Ответ:", 42)`, Go преобразует число `42` в строку `"42"` и склеивает с `"Ответ:"`. + +**Именно поэтому** в первом уроке мы говорили "один бинарник без зависимостей" — теперь вы понимаете, что это значит. Go кладёт всё необходимое прямо в файл. + +### Уменьшаем размер + +Зачем уменьшать размер? Меньший бинарник — это быстрее скачивание на сервер (особенно если серверов десятки), меньше места в Docker-образе, быстрее запуск контейнеров. Для Hello World разница незаметна, но когда проект вырастет до 20–50 МБ — экономия 25–30% уже ощутима. + +```bash +# Стандартная сборка +go build -o app . +# app — ~2 МБ + +# Без отладочной информации (-s: символы, -w: DWARF) +go build -ldflags="-s -w" -o app . +# app — ~1.5 МБ +``` + +Флаги `-s -w` убирают таблицу символов и отладочную информацию DWARF. На практике это **минус 25–30%** от размера. Паник-трейсы при этом продолжают работать — Go хранит свою внутреннюю таблицу для отслеживания ошибок отдельно. + +:::tip Рецепт для продакшена +```bash +go build -ldflags="-s -w" -trimpath -o app . +``` +Флаг `-trimpath` дополнительно вырезает из бинарника абсолютные пути вашей файловой системы. Бонус к безопасности и воспроизводимости сборки. +::: + +### Встраиваем версию + +Представьте: вы отправили бинарник на сервер. Через месяц что-то сломалось. Какая версия кода там стоит — вы уже не помните. Пересобирали с тех пор десять раз. Если бинарник сам умеет ответить "я версия 1.0.0" — проблема решается за секунду: запустили с флагом `--version`, сравнили с актуальной, поняли нужно ли обновлять. + +Именно для этого версию вшивают прямо в бинарник на этапе сборки: + +```go +package main + +import "fmt" + +var version = "dev" + +func main() { + fmt.Println("Версия:", version) +} +``` + +В коде `version` равна `"dev"` — значение по умолчанию для разработки. Но при сборке мы подменяем её: + +```bash +go build -ldflags="-X main.version=1.0.0" -o app . +./app +Версия: 1.0.0 +``` + +Флаг `-X` подменяет значение строковой переменной на этапе компиляции — в самом исходном коде ничего менять не надо. В автоматизированных системах сборки — CI/CD (Continuous Integration / Continuous Delivery, автоматическая сборка и доставка кода) — это стандартная практика: вшивают номер версии, дату сборки, идентификатор изменения из Git. Любой бинарник может "представиться". + +:::tip Начиная с Go 1.24 +Go автоматически вшивает информацию из Git (идентификатор изменения, тег версии, пометку `+dirty` если есть несохранённые правки). Посмотреть можно через `go version -m ./app`. +::: + +### Build cache — почему второй раз быстрее + +**Кэш** (cache) — место, где хранятся результаты предыдущей работы, чтобы не делать её заново. Браузер кэширует картинки с сайтов, чтобы не скачивать их повторно. Go делает то же самое с компиляцией: сохраняет уже скомпилированные пакеты, а при следующей сборке компилирует только те, что изменились. + +```bash +go build -o app . # Первый раз: ~2 секунды +go build -o app . # Второй раз: ~0.3 секунды (линковка) +``` + +Где именно Go хранит кэш на вашем компьютере, можно узнать командой: + +```bash +go env GOCACHE +``` + +Путь зависит от ОС — на Linux, macOS и Windows он будет разным. Если что-то пошло не так и хотите очистить кэш — `go clean -cache`. + +:::tip Для опытных: PGO — оптимизация по профилю +Начиная с Go 1.22, Profile-Guided Optimization стабильна. Идея: вы снимаете CPU-профиль работающего приложения (через `pprof`), кладёте файл `default.pgo` в корень проекта, и при следующей сборке компилятор использует реальные данные о горячих путях для агрессивного инлайнинга и оптимизации. Прирост производительности — **2–14%** без единого изменения в коде. Подробнее — в уроках про тестирование (Testing) и профилирование (Profiling). +::: + +--- + +## `go install` — ставим утилиты глобально + +`go build` создаёт бинарник в текущей папке — там, где вы сейчас стоите в терминале. Это удобно для вашего проекта, но что если вы хотите поставить **чужую утилиту** — линтер, форматтер, генератор кода — и пользоваться ей из любого места в системе? + +Для этого есть `go install`. Он делает то же, что `go build`, но кладёт готовый бинарник не рядом с вами, а в специальную папку. В уроке про установку мы видели переменную [`GOBIN`](/ru/installation/#настройка-переменных-окружения) в таблице — вот это она и есть. Узнать, куда именно Go кладёт утилиты на вашей системе, можно командой: + +```bash +go env GOPATH +``` + +Бинарники попадут в подпапку `bin` этого пути. Если вы при установке Go добавили эту папку в `PATH` — утилиты будут доступны из любой директории. Если нет — самое время это сделать (подробнее в уроке про установку). + +Пример — установим `goimports`, утилиту для автоматической расстановки импортов: + +```bash +go install golang.org/x/tools/cmd/goimports@latest +``` + +Эта команда скачивает исходный код `goimports` с `golang.org/x/tools`, компилирует его и кладёт готовый бинарник в ту самую папку `bin`. Суффикс `@latest` означает "последняя версия". Теперь утилита доступна глобально: + +```bash +goimports -w main.go +``` + +Это работает из любой папки — при условии, что папка `bin` добавлена в `PATH`. Именно так устанавливались инструменты в уроке про редактор — `gopls`, `dlv` и другие расширения VS Code ставились через `go install`. + +### Отличие от `go build` + +| | `go build` | `go install` | +|---|---|---| +| **Куда кладёт бинарник** | В текущую папку | В папку `bin` внутри `GOPATH` | +| **Для чего** | Ваш проект | Чужие утилиты | +| **Доступен глобально** | Нет | Да (если папка `bin` в `PATH`) | +| **Типичное использование** | `go build -o server .` | `go install tool@latest` | + +:::tip Для опытных: директива `tool` в go.mod (Go 1.24) +Раньше, чтобы зафиксировать версию утилиты (линтера, кодогенератора) на весь проект, использовали хак — файл `tools.go` с blank import-ами. С Go 1.24 появилась директива `tool` прямо в `go.mod`: + +```bash +go get -tool github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +Это добавит `tool` строку в `go.mod`, и теперь любой участник команды запускает утилиту через `go tool golangci-lint run ./...` — гарантированно та же версия, что и у всех. Никаких расхождений между разработчиками и CI. +::: + +### Когда что использовать + +| | `go run` | `go build` | `go install` | +|---|----------|------------|--------------| +| **Что делает** | Компилирует → запускает → удаляет | Компилирует → сохраняет | Компилирует → кладёт в `GOPATH/bin` | +| **Файл остаётся** | ❌ Нет | ✅ В текущей папке | ✅ В `GOPATH/bin` | +| **Когда использовать** | Разработка, эксперименты | Сборка для сервера, Docker | Установка CLI-утилит | +| **Кэширование** | С Go 1.24 | Промежуточные файлы | Промежуточные файлы | + +**Типичный рабочий процесс:** +1. `go run .` — пока пишете и отлаживаете +2. `go build` — когда нужен финальный артефакт +3. `go install` — для утилит, которыми пользуетесь постоянно + +--- + +## Кросс-компиляция — собираем под любую ОС + +Важный момент: каждый бинарник собирается **под конкретную ОС и архитектуру**. Бинарник, собранный на macOS, не запустится на Linux — получите ошибку `Exec format error`. И наоборот. Это не как Python-скрипт, который одинаково работает везде, где стоит интерпретатор. Бинарник содержит машинные инструкции для конкретной платформы. + +Если вы разрабатываете на macOS, а сервер работает на Linux — нужно собрать бинарник именно для Linux. Это и есть **кросс-компиляция**: сборка на одной платформе для другой. В первом уроке мы упоминали "собрал под Linux на маке одной командой" — пришло время показать, как. + +Помните код из урока про установку? + +```go +fmt.Printf("OS: %s\n", runtime.GOOS) +fmt.Printf("Arch: %s\n", runtime.GOARCH) +``` + +Тогда `runtime.GOOS` показывал вашу текущую ОС — и это логично, ведь вы собирали и запускали на одной машине. Но `runtime.GOOS` и `runtime.GOARCH` — это не "определение системы в момент запуска". Это константы, зашитые в бинарник на этапе компиляции. По умолчанию Go ставит туда вашу ОС и архитектуру, поэтому всё совпадает. Но их можно подменить двумя переменными окружения — и тогда бинарник будет собран для **другой** платформы: + +```bash +# Собираем на Mac, запускаем на Linux-сервере +GOOS=linux GOARCH=amd64 go build -o app-linux . + +# Для Windows +GOOS=windows GOARCH=amd64 go build -o app.exe . + +# Для Mac с Apple Silicon +GOOS=darwin GOARCH=arm64 go build -o app-mac . + +# Для Raspberry Pi +GOOS=linux GOARCH=arm GOARM=7 go build -o app-rpi . +``` + +Никаких дополнительных компиляторов, тулчейнов, виртуальных машин. Go всё умеет из коробки. + +Хотите увидеть все поддерживаемые платформы? Их больше 45: + +```bash +go tool dist list +aix/ppc64 +android/amd64 +darwin/amd64 +darwin/arm64 +js/wasm +linux/amd64 +linux/arm64 +wasip1/wasm +windows/amd64 +... и ещё 40+ +``` + +### `CGO_ENABLED=0` — полностью статический бинарник + +Мы говорили, что Go-бинарник самодостаточный — внутри всё необходимое. Но это не совсем так. По умолчанию Go в некоторых случаях использует **C-библиотеки** операционной системы — например, для работы с DNS (преобразование доменных имён в IP-адреса) и для получения информации о пользователях системы. Это называется **CGO** (C-Go) — мост между Go и кодом на языке C. + +Проблема в том, что C-библиотеки разные на разных системах. Бинарник, собранный с CGO, ожидает определённую C-библиотеку на целевой системе. Если её там нет или стоит другая версия — бинарник упадёт с непонятной ошибкой. + +Чтобы бинарник был **по-настоящему** самодостаточным и не зависел ни от чего на целевой системе, отключаем CGO: + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app . +``` + +`CGO_ENABLED=0` говорит Go: "не используй C-библиотеки, сделай всё сам". Go заменит системные вызовы своими реализациями на чистом Go. Результат — бинарник, которому нужна только операционная система. Никаких библиотек, никаких зависимостей. + +:::danger Это важно для Docker +Популярный базовый образ Alpine Linux использует `musl libc` вместо стандартной `glibc`. Если собрать бинарник без `CGO_ENABLED=0` — в Alpine он упадёт с загадочной ошибкой `no such file or directory`, хотя файл на месте. Система не может найти C-библиотеку, которую бинарник ожидает. Правило простое: **для Docker-сборок всегда ставьте `CGO_ENABLED=0`**. +::: + +--- + +## Что под капотом: почему Go компилируется за секунды + +В первом уроке мы говорили, что Go создали, потому что создатели устали ждать компиляцию C++ по 45 минут. Вот как они решили эту проблему. + +### Этапы компиляции (на пальцах) + +Когда вы набираете `go build`, происходит следующее: + +![Пайплайн компиляции Go](/images/go-compilation-pipeline.png) + +1. **Lexer (Scanner)** — разбивает текст на токены: ключевые слова, имена, числа, операторы +2. **Parser** — собирает токены в дерево (AST — Abstract Syntax Tree), отражающее структуру программы +3. **Semantic Analysis (Type Checker)** — проверяет типы: "x — это int, нельзя сложить со строкой" +4. **Intermediate Representation** — преобразует AST в промежуточное представление (IR) +5. **SSA (Static Single Assignment)** — оптимизирует IR: удаляет мёртвый код, сворачивает константы +6. **Machine Code Generation** — превращает SSA в инструкции для конкретного процессора +7. **Linker** — склеивает машинный код с runtime → готовый бинарник +8. **Execution** — ОС загружает бинарник и запускает + +Вы не обязаны понимать каждый шаг. Важно знать, что компиляция — это **перевод**: человеческий текст → машинные инструкции. Go делает этот перевод очень быстро. + +:::tip Для опытных: что происходит на каждом этапе +**SSA (Static Single Assignment)** — ключевое промежуточное представление. Каждой переменной присваивается значение ровно один раз: `x = 1; x = x + 2` превращается в `x₁ = 1; x₂ = x₁ + 2`. Это упрощает оптимизации: удаление мёртвого кода, свёртку констант, устранение лишних проверок границ массивов. + +**Escape analysis** — компилятор решает, где жить переменной: на стеке (быстро, бесплатная очистка) или в куче (дороже, нагружает GC — Garbage Collector, сборщик мусора, который автоматически освобождает неиспользуемую память). Если ссылка на переменную "утекает" за пределы функции — куча. Иначе — стек. Посмотреть решения компилятора: `go build -gcflags="-m" .` + +**Inlining** — компилятор подставляет тело маленьких функций прямо в место вызова, убирая накладные расходы. Go инлайнит функции до определённой "стоимости" (80 узлов AST). С Go 1.22 инлайнер стал агрессивнее. +::: + +:::tip Для опытных: что происходит до `main()` +Когда ОС запускает Go-бинарник, до вашего `main()` далеко: + +1. **OS Loader** загружает файл в память, находит точку входа +2. **Ассемблерный bootstrap** (`_rt0_amd64_linux` и подобные) сохраняет argc/argv +3. **`runtime.rt0_go`** выделяет стек для системной горутины g0, инициализирует кучу +4. **Запуск подсистем** — GC, планировщик горутин, сетевой поллер +5. **`init()` функции** всех импортированных пакетов — снизу вверх по дереву зависимостей +6. **`main.main()`** — наконец-то ваш код + +Именно поэтому даже пустая Go-программа "тяжелее" аналога на C — она несёт с собой полноценную среду выполнения. +::: + +### Почему быстрее C++ и Rust + +**Главная причина — модель зависимостей.** Роб Пайк измерял: при компиляции Go-кода компилятор читает в **40 раз** меньше исходного текста, чем при компиляции C++. В C++ каждый `#include ` заново "объясняет" компилятору, что такое строки. В Go информация о пакете хранится в скомпилированном виде — компилятор читает только прямые импорты, не проваливаясь в их зависимости. + +**Другие факторы:** +- **25 ключевых слов** — парсер работает мгновенно +- **Запрет циклических импортов** — граф зависимостей можно компилировать параллельно +- **Неиспользуемый импорт = ошибка** — компилятор не тратит время на мёртвый код +- **Нет шаблонов как в C++** — не нужно раздувать код при инстанциации + +Для масштаба: проект Istio (платформа для управления микросервисами, ~350 000 строк Go) компилируется с нуля за **33 секунды** на мощной машине. С прогретым кэшем — меньше секунды. + +:::tip Для любопытных +`go build -x` покажет каждую команду, которую выполняет тулчейн: компиляцию каждого пакета, генерацию конфигов, линковку. Десятки строк вывода — и всё это за пару секунд. +::: + +--- + +## Практические сценарии + +### Docker multi-stage build + +Стандартный способ доставки Go-сервиса на сервер — многоэтапная сборка Docker: + +```dockerfile +# Этап 1: собираем бинарник +FROM golang:1.25 AS builder +WORKDIR /app +COPY go.mod go.sum ./ # go.sum — файл с контрольными суммами зависимостей +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -trimpath -o server . + +# Этап 2: минимальный образ +FROM scratch +COPY --from=builder /app/server /server +ENTRYPOINT ["/server"] +``` + +Образ `scratch` — абсолютно пустой. Ни bash, ни curl, ни libc. Только ваш бинарник. Финальный образ: **3–5 МБ** вместо 700 МБ+ с полным SDK (Software Development Kit — набор инструментов для разработки). Это возможно именно потому, что Go-бинарник самодостаточный. + +### Makefile + +Makefile — это файл с набором команд-рецептов. Вместо того чтобы каждый раз набирать длинную команду сборки с десятком флагов, вы описываете её один раз в Makefile и потом вызываете коротким `make build`. Утилита `make` есть на Linux и macOS из коробки, для Windows можно установить отдельно. + +Для проектов побольше удобно завернуть команды в Makefile: + +```makefile +APP_NAME = myapp +.PHONY: run build test clean cross + +run: + go run . +build: + go build -ldflags="-s -w" -trimpath -o $(APP_NAME) . +test: + go test -race ./... +clean: + rm -f $(APP_NAME) $(APP_NAME)-* +cross: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(APP_NAME)-linux . + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o $(APP_NAME)-mac . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o $(APP_NAME).exe . +``` + +Теперь `make build` вместо длинной команды с флагами. `make cross` — сборка под три ОС разом. + +:::tip Для опытных: GoReleaser +Когда проект дорастёт до публичных релизов — кросс-компиляция, архивы, changelog, публикация на GitHub/GitLab, конфиги для Homebrew и Scoop — вручную это ад. **GoReleaser** автоматизирует весь цикл. Один файл `.goreleaser.yaml`, одна команда — и релиз под десятки платформ готов. Его используют Kubernetes, Docker, GitHub CLI и тысячи open-source проектов. +::: + +--- + +## Типичные грабли новичков + +### 1. "undefined" при `go run main.go` + +Когда проект подрастёт, код естественно разобьётся на несколько файлов — например, `main.go` и `math.go` в одном пакете `main`. И тут есть нюанс. + +Когда вы явно указываете файлы — `go run main.go` — Go считает только перечисленные файлы частью вашего пакета. Остальные `.go`-файлы в той же директории он не видит, как будто их нет: + +```bash +go run main.go +./main.go:10:2: undefined: Add # функция Add из math.go — "не существует" + +go run . # ✅ все файлы пакета включены +``` + +Важно не путать две вещи: +- **Соседние файлы того же пакета** (например `math.go` с `package main` в той же папке) — при `go run main.go` **не подтягиваются**. Это и есть проблема. +- **Импортированные пакеты** (через `import`) — подтягиваются нормально. Если у вас `import "myproject/utils"`, пакет `utils` скомпилируется, потому что он разрешается через модульную систему, а не через "соседние файлы в директории". + +`go run .` и `go build .` включают **все `.go`-файлы** в директории (кроме `_test.go`). Поэтому привыкайте к `go run .` — с ней таких проблем не будет. + +### 2. "cannot run non-main package" + +```bash +go run . +go run: cannot run non-main package +``` + +Забыли написать `package main` или нет функции `main()`. Помните из прошлого урока: только `package main` + `func main()` создают исполняемую программу. + +### 3. Бинарник не запускается на другой ОС + +Собрали на Mac, скопировали на Linux-сервер: + +```bash +./myapp +bash: ./myapp: cannot execute binary file: Exec format error +``` + +Это бинарник для macOS, а запускаете на Linux. Нужна кросс-компиляция: + +```bash +GOOS=linux GOARCH=amd64 go build -o myapp . +``` + +### 4. "permission denied" (Linux/macOS) + +```bash +./myapp +bash: ./myapp: Permission denied +``` + +Нет прав на выполнение. `go build` ставит их автоматически, но при копировании через архив или сеть бит выполнения может потеряться. На Windows такой проблемы нет — там работает по-другому. + +```bash +chmod +x myapp +``` + +### 5. Загадочная "no such file or directory" в Docker + +```bash +exec /server: no such file or directory +``` + +Файл на месте, но система не может найти динамический загрузчик `glibc`. Собрали без `CGO_ENABLED=0`, а запускаете в Alpine с `musl`. + +```bash +# Правильная сборка для Docker: +CGO_ENABLED=0 go build -o server . +``` + +--- + +## Полный пример: от кода до бинарника + +Возьмём программу greeter из прошлого урока и пройдём полный цикл: + +```go +// main.go +package main + +import ( + "fmt" + "os" + "strings" +) + +var version = "dev" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Println(version) + return + } + + name := "Мир" + if len(os.Args) > 1 { + name = strings.Join(os.Args[1:], " ") + } + fmt.Printf("Привет, %s!\n", name) +} +``` + +```bash +# 1. Быстрый запуск во время разработки +go run . Вася +Привет, Вася! + +# 2. Собираем бинарник с версией +go build -ldflags="-s -w -X main.version=1.0.0" -trimpath -o greeter . + +# 3. Проверяем +./greeter --version +1.0.0 +./greeter дорогой друг +Привет, дорогой друг! + +# 4. Кросс-компиляция для Linux-сервера +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -ldflags="-s -w -X main.version=1.0.0" -trimpath -o greeter-linux . + +# 5. Оба файла — около 1.3 МБ каждый +``` + +Один исходник — бинарники под любую платформу. Без Docker, без виртуальных машин, без боли. + +--- + +## Итоги + +| Команда / Концепция | Что помнить | +|---------------------|-------------| +| `go run .` | Компилирует и запускает. Бинарник временный. Для разработки. | +| `go build -o app .` | Создаёт постоянный бинарник. Для деплоя и CI/CD. | +| `go install` | Ставит бинарник в `GOPATH/bin`. Для CLI-утилит. | +| `-ldflags="-s -w"` | Убирает отладочную инфу. Минус 25–30% размера. | +| `-trimpath` | Убирает пути. Безопасность + воспроизводимость. | +| `-X main.var=val` | Вшивает значение переменной при сборке. | +| `GOOS` / `GOARCH` | Кросс-компиляция. Любая ОС, любая архитектура. | +| `CGO_ENABLED=0` | Полностью статический бинарник. Обязателен для Docker. | +| `go build -x` | Показывает все шаги компиляции. Для любопытных. | + +--- + +## Задачи + +### Задача 1: Заглядываем под капот ⭐ + +Запустите программу Hello World так, чтобы увидеть путь к временной директории сборки. Временные файлы не должны удаляться после запуска. + +
+Решение + +```bash +go run -work main.go +WORK=/var/folders/.../go-build1234567890 # путь зависит от ОС +Привет, мир! +``` + +Флаг `-work` печатает путь и сохраняет временную директорию. Можно зайти в неё и найти скомпилированный бинарник. + +
+ +### Задача 2: Оптимальная сборка ⭐⭐ + +Соберите бинарник для Linux ARM64 с минимальным размером, без путей файловой системы и без зависимостей от C-библиотек. Имя файла — `server`. + +
+Решение + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ + go build -ldflags="-s -w" -trimpath -o server . +``` + +- `CGO_ENABLED=0` — статический бинарник без C-зависимостей +- `GOOS=linux GOARCH=arm64` — целевая платформа +- `-ldflags="-s -w"` — убираем символы и DWARF +- `-trimpath` — убираем абсолютные пути + +
+ +### Задача 3: Версия из командной строки ⭐⭐⭐ + +Напишите программу, которая при запуске с флагом `--version` выводит номер версии, а без флага — приветствие. Версия должна вшиваться при сборке через `-ldflags`, а не хардкодиться. + +```bash +go build -ldflags="-X main.version=2.1.0" -o app . +./app --version +app v2.1.0 +./app +Привет из app! +``` + +
+Подсказка + +Объявите `var version = "dev"` и используйте `os.Args` для проверки аргументов. + +
+ +
+Решение + +```go +package main + +import ( + "fmt" + "os" +) + +var version = "dev" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Println("app v" + version) + return + } + fmt.Println("Привет из app!") +} +``` + +Сборка: + +```bash +go build -ldflags="-X main.version=2.1.0" -o app . +``` + +
+ +### Задача 4: Мультиплатформенная сборка ⭐⭐⭐ + +Напишите скрипт (или команды), который соберёт один и тот же проект под три платформы: Linux amd64, macOS arm64, Windows amd64. Все бинарники должны быть статическими, минимального размера и лежать в папке `dist/`. + +
+Решение + +```bash +mkdir -p dist + +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app-linux . +CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o dist/app-mac . +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app.exe . +``` + +Или через Makefile: + +```makefile +.PHONY: dist +dist: + mkdir -p dist + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app-linux . + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o dist/app-mac . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app.exe . +``` + +
+ +--- + +## Что дальше? + +Теперь вы умеете превращать код в бинарник, собирать под любую платформу и понимаете, что происходит за кулисами компиляции. В следующем уроке разберём **Go Playground** — веб-среду для запуска Go-кода прямо в браузере: быстрые эксперименты, обмен сниппетами с коллегами и тестирование идей без локальной установки. + +--- + +## Источники + +- [Go Command Documentation](https://pkg.go.dev/cmd/go) — официальная документация команды `go` +- [Go at Google: Language Design in the Service of Software Engineering](https://go.dev/talks/2012/splash.article) — Роб Пайк о дизайне Go +- [Go 1.24 Release Notes](https://go.dev/doc/go1.24) — кэширование `go run`, директива `tool` +- [Go 1.25 Release Notes](https://go.dev/doc/go1.25) — DWARF v5, автоматический GOMAXPROCS +- [How to Reduce Go Binary Size](https://words.filippo.io/shrink-your-go-binaries-with-this-one-weird-trick/) — Filippo Valsorda об оптимизации размера +- [Effective Go](https://go.dev/doc/effective_go) — рекомендации по идиоматичному Go +- [Understanding Go Compiler](https://www.linkedin.com/pulse/understanding-go-compiler-kanishka-naik-sbmwc) — Kanishka Naik о пайплайне компиляции Go + + +--- + + diff --git a/apps/web/src/content/docs/ru/program-structure.md b/apps/web/src/content/docs/ru/program-structure.md index 85daada..9990a20 100644 --- a/apps/web/src/content/docs/ru/program-structure.md +++ b/apps/web/src/content/docs/ru/program-structure.md @@ -868,5 +868,8 @@ func main() { ← Предыдущий Hello World -
+ + Следующий → + Компиляция и запуск + diff --git a/apps/web/src/data/sidebar-unified.json b/apps/web/src/data/sidebar-unified.json index 6a198f5..18fb9fb 100644 --- a/apps/web/src/data/sidebar-unified.json +++ b/apps/web/src/data/sidebar-unified.json @@ -47,6 +47,10 @@ "en": "Program Structure" }, "slug": "program-structure" + }, + { + "label": "Компиляция и запуск", + "slug": "compile-and-run" } ] } diff --git a/apps/web/src/data/sidebar.json b/apps/web/src/data/sidebar.json index be2a7a4..cf133e6 100644 --- a/apps/web/src/data/sidebar.json +++ b/apps/web/src/data/sidebar.json @@ -27,6 +27,10 @@ { "label": "Структура программы", "link": "/ru/program-structure/" + }, + { + "label": "Компиляция и запуск", + "link": "/ru/compile-and-run/" } ] } diff --git a/apps/web/src/data/toc.json b/apps/web/src/data/toc.json index 38a56bd..584ec3e 100644 --- a/apps/web/src/data/toc.json +++ b/apps/web/src/data/toc.json @@ -31,6 +31,11 @@ "slug": "program-structure", "title": "Структура программы", "number": "1.5" + }, + { + "slug": "compile-and-run", + "title": "Компиляция и запуск", + "number": "1.6" } ] } diff --git a/books/go-language-en/content/01-intro/03-installation.md b/books/go-language-en/content/01-intro/03-installation.md index bed4a34..b054dfd 100644 --- a/books/go-language-en/content/01-intro/03-installation.md +++ b/books/go-language-en/content/01-intro/03-installation.md @@ -122,7 +122,7 @@ Key variables: | `GOPATH` | Working directory | `~/go` | | `GOBIN` | Path for binaries | `~/go/bin` | -## First Programme +## First Program Let's create a simple programme to verify the installation: diff --git a/books/go-language-ru/content/01-intro/06-compile-and-run.md b/books/go-language-ru/content/01-intro/06-compile-and-run.md index ff29f84..a32f9e9 100644 --- a/books/go-language-ru/content/01-intro/06-compile-and-run.md +++ b/books/go-language-ru/content/01-intro/06-compile-and-run.md @@ -2,19 +2,824 @@ title: "Компиляция и запуск" description: "go build, go run и исполняемые файлы" slug: compile-and-run -published: false +published: true author: godojo -updatedAt: "2025-12-15" +updatedAt: "2026-02-20" +readingTime: 14 --- -# Компиляция и запуск +# Компиляция и запуск: что на самом деле происходит, когда вы запускаете Go-программу -> TODO: Описание +В предыдущем уроке мы разобрали, из каких деталей состоит Go-программа. Вы уже десяток раз набирали `go run main.go` и видели результат. Но что стоит за этой командой? Куда девается скомпилированный файл? И как превратить код в **бинарник** — исполняемый файл с машинными инструкциями, который можно просто скопировать на сервер и запустить — без Go, без зависимостей, без ничего? -## Введение +В первом уроке мы упоминали "компилируемый язык" и "один бинарник без зависимостей" — пришло время разобраться, что за этими словами стоит на практике. Компиляция — это перевод вашего кода в язык процессора: нули и единицы, которые CPU выполняет напрямую. Результат этого перевода — тот самый бинарник (от слова binary — двоичный). В отличие от Python, где интерпретатор каждый раз читает и выполняет текст программы строка за строкой, Go делает перевод один раз — а дальше процессор работает с готовым результатом. Отсюда и скорость. -TODO +Сегодня снимаем капот. + +--- + +Для всех примеров в этом уроке будем использовать знакомый `main.go`: + +```go +package main + +import "fmt" + +func main() { + fmt.Println("Привет, мир!") +} +``` + +Откройте терминал в VS Code (`` Ctrl+` ``) и убедитесь, что вы в папке с этим файлом. + +--- + +## `go run` — иллюзия интерпретатора + +Если вы пришли из Python или JavaScript, `go run` ощущается привычно: набрал команду — программа заработала. Никаких промежуточных файлов, никакой возни. Кажется, что Go просто выполняет текст. + +Но Go — компилируемый язык. Всегда. Даже когда `go run` прикидывается интерпретатором, под капотом разворачивается полноценная сборка: + +1. Создаётся временная директория (путь зависит от ОС) +2. Исходный код компилируется в нативный бинарник +3. Бинарник запускается как отдельный процесс +4. После завершения программы временная директория удаляется + +Именно поэтому в рабочей папке ничего не появляется — всё живёт и умирает во временном каталоге. + +### Заглянуть под капот + +Хотите увидеть, что происходит? Добавьте флаг `-x`: + +```bash +go run -x main.go +``` + +В терминале посыплются десятки строк — каждая команда, которую Go выполняет за кулисами. Там будет и путь к временной директории (`WORK=...`), и вызовы компилятора, и линковка. + +А если хотите сохранить временные файлы для изучения — есть флаг `-work`: + +```bash +go run -work main.go +WORK=/var/folders/.../go-build3712456890 # путь зависит от ОС +Привет, мир! +``` + +Директория не удалится после завершения. Можете зайти туда и найти настоящий скомпилированный бинарник. + +:::tip Лайфхак +`go run -work` — отличный способ убедиться, что Go действительно создаёт полноценный исполняемый файл, а не "интерпретирует" ваш код. +::: + +### Передача аргументов + +В прошлом уроке мы писали программу greeter с `os.Args`. Запуск выглядел так: + +```bash +go run main.go Вася +Привет, Вася! +``` + +Всё что после имени файла — аргументы вашей программы. Go сам разберётся, где заканчиваются его флаги и начинаются ваши данные. Флаги Go идут **до** файла, аргументы программы — **после**: + +```bash +go run -race main.go --port 8080 +# ^^^^ ^^^^^^^^^^ +# флаг Go аргументы вашей программы +``` + +Кстати, вместо `go run main.go` можно писать **`go run .`** — точка означает "весь пакет в текущей директории". Пока у вас один файл — разницы нет, но это более идиоматичный* подход, и позже вы поймёте почему. + +:::caution Точка работает только с Go-модулем +Команды с `.` (`go run .`, `go build .`) требуют файла `go.mod` в директории вашего проекта. Если его нет, Go выдаст ошибку. Создайте модуль одной командой: + +```bash +go mod init hello +``` + +`hello` — это имя вашего модуля (можно любое). После этого в директории появится файл `go.mod`. Подробнее про модули поговорим в отдельном уроке, а пока достаточно знать: **перед первым использованием `.` выполните `go mod init`**. +::: + +* **Идиоматичный** — значит "так принято в сообществе". У каждого языка есть негласные правила: не просто "работает", а "так пишут опытные разработчики". В Go-мире `go run .` — идиоматично, `go run main.go` — нет. Вы будете часто встречать это слово в Go-документации и статьях. + +### Ограничения + +С версии **Go 1.24** у `go run` появилось кэширование: если код не менялся, повторный запуск берёт готовый бинарник из кэша. Но ограничений всё равно хватает: + +- **Медленнее прямого запуска.** Даже с кэшем `go run` проверяет актуальность сборки при каждом вызове. Для крупных проектов накладные расходы могут быть значительными — в некоторых случаях до 8 раз медленнее, чем запуск готового бинарника. +- **Нет контроля над бинарником.** Он лежит где-то в кэше, вы не можете его передать или отправить на сервер. `os.Executable()` вернёт путь во временную директорию — если ваша программа полагается на свой путь, это сломается. +- **Кэш недолговечен.** Go агрессивно чистит кэшированные бинарники — примерно через 2 дня неиспользования. Утром `go run` может снова компилировать с нуля. +- **Только `package main`.** Нельзя запустить библиотечный пакет — только исполняемые программы. +- **Кросс-компиляция бессмысленна.** `GOOS=linux go run .` не имеет смысла — бинарник запускается на вашей машине, а не на целевой платформе. +- **Не для продакшена.** Продакшен (production) — это среда, где ваша программа работает "по-настоящему": обслуживает реальных пользователей, крутится на сервере 24/7. В противоположность — среда разработки (development), где вы пишете и тестируете код на своём компьютере. `go run` — инструмент разработки, не способ деплоя в продакшен. + +**Вывод:** `go run` — это "быстро глянуть". Для всего остального есть `go build`. + +--- + +## `go build` — создаём настоящий бинарник + +Когда нужен файл, который можно отправить коллеге, загрузить на сервер или положить в Docker-контейнер (Docker — система для упаковки и запуска приложений в изолированных средах, подробнее познакомимся позже) — используйте `go build`. + +```bash +go build -o myapp . +./myapp +Привет, мир! +``` + +В директории появился файл `myapp` (на Windows — `myapp.exe`) размером около 2 МБ или чуть больше. Это самодостаточный бинарник. На целевой машине не нужен Go, не нужны библиотеки, не нужен рантайм. Просто копируете файл и запускаете. + +### Имя выходного файла + +Без флага `-o` Go сам выберет имя: + +```bash +go build . # Имя из go.mod (или имя директории) +go build -o server . # Явно задаём имя +go build -o bin/app . # Можно указать и путь +``` + +На Windows автоматически добавится `.exe`. + +:::tip `go build` без `package main` +Если запустить `go build .` в директории с библиотечным пакетом (не `main`), Go скомпилирует код и проверит его на ошибки, но **не создаст выходного файла**. Удобный способ валидировать код без засорения директории — особенно в CI. +::: + +### Флаг `-race` — детектор гонок данных + +Представьте: два человека одновременно редактируют один документ, не видя друг друга. Один пишет заголовок, другой его удаляет — результат непредсказуем. В программировании это называется **гонка данных** (data race) — когда несколько частей программы одновременно читают и изменяют одну и ту же переменную. Результат зависит от того, кто успел первым, и каждый запуск может давать разный результат. + +Go умеет такие ситуации находить автоматически. Вот пример — не пытайтесь пока разобрать каждую строку, ключевое слово `go` мы изучим в уроке про конкурентность (Concurrency). Сейчас важен сам принцип: + +```go +package main + +import "fmt" + +func main() { + count := 0 + for i := 0; i < 1000; i++ { + go func() { // запускаем 1000 параллельных задач + count++ // все пишут в одну переменную + }() + } + fmt.Println(count) +} +``` + +Запустим без флага — программа молча выдаст непредсказуемый результат: + +```bash +go run . +0 # или 127, или 999 — каждый раз по-разному +``` + +Теперь с `-race`: + +```bash +go run -race main.go +================== +WARNING: DATA RACE +Read at 0x00c00011c028 by goroutine 9: + main.main.func1() + /home/user/main.go:9 +0x2e + +Previous write at 0x00c00011c028 by goroutine 8: + main.main.func1() + /home/user/main.go:9 +0x44 + +Goroutine 9 (running) created at: + main.main() + /home/user/main.go:8 +0x4a + +Goroutine 8 (finished) created at: + main.main() + /home/user/main.go:8 +0x4a +================== +... ещё 2 похожих предупреждения ... +651 +Found 3 data race(s) +exit status 66 +``` + +Go нашёл **3 гонки** и точно показывает: строка 9, переменная `count` (адрес `0x00c00011c028`), несколько параллельных задач пишут в неё одновременно. Программа завершилась с кодом 66 — специальный код ошибки для гонок. Число `651` вместо ожидаемых `1000` — результат потерянных обновлений, типичное последствие гонки данных. Без `-race` программа бы тихо выдавала неправильные результаты — баг, который крайне сложно поймать вручную. + +Флаг `-race` работает и с `go build`, и с `go run`, и с `go test`. Бинарник с ним медленнее и потребляет больше памяти, поэтому в продакшен его не ставят. Но в тестах и при разработке — обязательная практика. + +### Почему Hello World весит 2 мегабайта? + +После первого `go build` новички удивляются: программа из пяти строк — и 2 МБ? На C аналог весит 16 КБ. + +Дело в том, что Go-бинарник — это не просто ваш код. Это целая вселенная: + +- **Go runtime** — среда выполнения +- **Garbage Collector** — сборщик мусора +- **Goroutine Scheduler** — планировщик горутин (помните из первого урока — многозадачность из коробки?) +- **Таблица символов и отладочная информация** — для паник-трейсов и отладки +- Части стандартной библиотеки, которые вы импортировали + +Один только `import "fmt"` тянет за собой рефлексию*, I/O* и форматирование строк*. Всё встроено внутрь. + +- * **Рефлексия** (Reflection) — механизм, позволяющий программе анализировать и изменять собственную структуру во время выполнения: узнавать типы переменных, читать их значения, вызывать функции по имени. Проще говоря — способность программы "смотреть на саму себя". Вспомните: вы пишете `fmt.Println(42)` — и получаете `42`. Пишете `fmt.Println("привет")` — получаете `привет`. Пишете `fmt.Println(3.14)` — получаете `3.14`. Откуда `Println` знает, как напечатать каждое из этих значений, если число и строка — совершенно разные вещи? Именно через рефлексию: в момент вызова функция спрашивает у рантайма "что мне передали — число? строку? что-то ещё?" — и на основе ответа выбирает, как это отобразить. Без рефлексии пришлось бы писать отдельную функцию для каждого типа данных. +- * **I/O** (Input/Output, ввод/вывод) — всё что связано с чтением и записью: вывод в терминал, чтение файлов, передача данных по сети. `fmt.Println` записывает текст в стандартный поток вывода (stdout) — это I/O. +- * **Форматирование строк** — превращение данных в читаемый текст. Когда вы пишете `fmt.Println("Ответ:", 42)`, Go преобразует число `42` в строку `"42"` и склеивает с `"Ответ:"`. + +**Именно поэтому** в первом уроке мы говорили "один бинарник без зависимостей" — теперь вы понимаете, что это значит. Go кладёт всё необходимое прямо в файл. + +### Уменьшаем размер + +Зачем уменьшать размер? Меньший бинарник — это быстрее скачивание на сервер (особенно если серверов десятки), меньше места в Docker-образе, быстрее запуск контейнеров. Для Hello World разница незаметна, но когда проект вырастет до 20–50 МБ — экономия 25–30% уже ощутима. + +```bash +# Стандартная сборка +go build -o app . +# app — ~2 МБ + +# Без отладочной информации (-s: символы, -w: DWARF) +go build -ldflags="-s -w" -o app . +# app — ~1.5 МБ +``` + +Флаги `-s -w` убирают таблицу символов и отладочную информацию DWARF. На практике это **минус 25–30%** от размера. Паник-трейсы при этом продолжают работать — Go хранит свою внутреннюю таблицу для отслеживания ошибок отдельно. + +:::tip Рецепт для продакшена +```bash +go build -ldflags="-s -w" -trimpath -o app . +``` +Флаг `-trimpath` дополнительно вырезает из бинарника абсолютные пути вашей файловой системы. Бонус к безопасности и воспроизводимости сборки. +::: + +### Встраиваем версию + +Представьте: вы отправили бинарник на сервер. Через месяц что-то сломалось. Какая версия кода там стоит — вы уже не помните. Пересобирали с тех пор десять раз. Если бинарник сам умеет ответить "я версия 1.0.0" — проблема решается за секунду: запустили с флагом `--version`, сравнили с актуальной, поняли нужно ли обновлять. + +Именно для этого версию вшивают прямо в бинарник на этапе сборки: + +```go +package main + +import "fmt" + +var version = "dev" + +func main() { + fmt.Println("Версия:", version) +} +``` + +В коде `version` равна `"dev"` — значение по умолчанию для разработки. Но при сборке мы подменяем её: + +```bash +go build -ldflags="-X main.version=1.0.0" -o app . +./app +Версия: 1.0.0 +``` + +Флаг `-X` подменяет значение строковой переменной на этапе компиляции — в самом исходном коде ничего менять не надо. В автоматизированных системах сборки — CI/CD (Continuous Integration / Continuous Delivery, автоматическая сборка и доставка кода) — это стандартная практика: вшивают номер версии, дату сборки, идентификатор изменения из Git. Любой бинарник может "представиться". + +:::tip Начиная с Go 1.24 +Go автоматически вшивает информацию из Git (идентификатор изменения, тег версии, пометку `+dirty` если есть несохранённые правки). Посмотреть можно через `go version -m ./app`. +::: + +### Build cache — почему второй раз быстрее + +**Кэш** (cache) — место, где хранятся результаты предыдущей работы, чтобы не делать её заново. Браузер кэширует картинки с сайтов, чтобы не скачивать их повторно. Go делает то же самое с компиляцией: сохраняет уже скомпилированные пакеты, а при следующей сборке компилирует только те, что изменились. + +```bash +go build -o app . # Первый раз: ~2 секунды +go build -o app . # Второй раз: ~0.3 секунды (линковка) +``` + +Где именно Go хранит кэш на вашем компьютере, можно узнать командой: + +```bash +go env GOCACHE +``` + +Путь зависит от ОС — на Linux, macOS и Windows он будет разным. Если что-то пошло не так и хотите очистить кэш — `go clean -cache`. + +:::tip Для опытных: PGO — оптимизация по профилю +Начиная с Go 1.22, Profile-Guided Optimization стабильна. Идея: вы снимаете CPU-профиль работающего приложения (через `pprof`), кладёте файл `default.pgo` в корень проекта, и при следующей сборке компилятор использует реальные данные о горячих путях для агрессивного инлайнинга и оптимизации. Прирост производительности — **2–14%** без единого изменения в коде. Подробнее — в уроках про тестирование (Testing) и профилирование (Profiling). +::: + +--- + +## `go install` — ставим утилиты глобально + +`go build` создаёт бинарник в текущей папке — там, где вы сейчас стоите в терминале. Это удобно для вашего проекта, но что если вы хотите поставить **чужую утилиту** — линтер, форматтер, генератор кода — и пользоваться ей из любого места в системе? + +Для этого есть `go install`. Он делает то же, что `go build`, но кладёт готовый бинарник не рядом с вами, а в специальную папку. В уроке про установку мы видели переменную [`GOBIN`](/ru/installation/#настройка-переменных-окружения) в таблице — вот это она и есть. Узнать, куда именно Go кладёт утилиты на вашей системе, можно командой: + +```bash +go env GOPATH +``` + +Бинарники попадут в подпапку `bin` этого пути. Если вы при установке Go добавили эту папку в `PATH` — утилиты будут доступны из любой директории. Если нет — самое время это сделать (подробнее в уроке про установку). + +Пример — установим `goimports`, утилиту для автоматической расстановки импортов: + +```bash +go install golang.org/x/tools/cmd/goimports@latest +``` + +Эта команда скачивает исходный код `goimports` с `golang.org/x/tools`, компилирует его и кладёт готовый бинарник в ту самую папку `bin`. Суффикс `@latest` означает "последняя версия". Теперь утилита доступна глобально: + +```bash +goimports -w main.go +``` + +Это работает из любой папки — при условии, что папка `bin` добавлена в `PATH`. Именно так устанавливались инструменты в уроке про редактор — `gopls`, `dlv` и другие расширения VS Code ставились через `go install`. + +### Отличие от `go build` + +| | `go build` | `go install` | +|---|---|---| +| **Куда кладёт бинарник** | В текущую папку | В папку `bin` внутри `GOPATH` | +| **Для чего** | Ваш проект | Чужие утилиты | +| **Доступен глобально** | Нет | Да (если папка `bin` в `PATH`) | +| **Типичное использование** | `go build -o server .` | `go install tool@latest` | + +:::tip Для опытных: директива `tool` в go.mod (Go 1.24) +Раньше, чтобы зафиксировать версию утилиты (линтера, кодогенератора) на весь проект, использовали хак — файл `tools.go` с blank import-ами. С Go 1.24 появилась директива `tool` прямо в `go.mod`: + +```bash +go get -tool github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +Это добавит `tool` строку в `go.mod`, и теперь любой участник команды запускает утилиту через `go tool golangci-lint run ./...` — гарантированно та же версия, что и у всех. Никаких расхождений между разработчиками и CI. +::: + +### Когда что использовать + +| | `go run` | `go build` | `go install` | +|---|----------|------------|--------------| +| **Что делает** | Компилирует → запускает → удаляет | Компилирует → сохраняет | Компилирует → кладёт в `GOPATH/bin` | +| **Файл остаётся** | ❌ Нет | ✅ В текущей папке | ✅ В `GOPATH/bin` | +| **Когда использовать** | Разработка, эксперименты | Сборка для сервера, Docker | Установка CLI-утилит | +| **Кэширование** | С Go 1.24 | Промежуточные файлы | Промежуточные файлы | + +**Типичный рабочий процесс:** +1. `go run .` — пока пишете и отлаживаете +2. `go build` — когда нужен финальный артефакт +3. `go install` — для утилит, которыми пользуетесь постоянно + +--- + +## Кросс-компиляция — собираем под любую ОС + +Важный момент: каждый бинарник собирается **под конкретную ОС и архитектуру**. Бинарник, собранный на macOS, не запустится на Linux — получите ошибку `Exec format error`. И наоборот. Это не как Python-скрипт, который одинаково работает везде, где стоит интерпретатор. Бинарник содержит машинные инструкции для конкретной платформы. + +Если вы разрабатываете на macOS, а сервер работает на Linux — нужно собрать бинарник именно для Linux. Это и есть **кросс-компиляция**: сборка на одной платформе для другой. В первом уроке мы упоминали "собрал под Linux на маке одной командой" — пришло время показать, как. + +Помните код из урока про установку? + +```go +fmt.Printf("OS: %s\n", runtime.GOOS) +fmt.Printf("Arch: %s\n", runtime.GOARCH) +``` + +Тогда `runtime.GOOS` показывал вашу текущую ОС — и это логично, ведь вы собирали и запускали на одной машине. Но `runtime.GOOS` и `runtime.GOARCH` — это не "определение системы в момент запуска". Это константы, зашитые в бинарник на этапе компиляции. По умолчанию Go ставит туда вашу ОС и архитектуру, поэтому всё совпадает. Но их можно подменить двумя переменными окружения — и тогда бинарник будет собран для **другой** платформы: + +```bash +# Собираем на Mac, запускаем на Linux-сервере +GOOS=linux GOARCH=amd64 go build -o app-linux . + +# Для Windows +GOOS=windows GOARCH=amd64 go build -o app.exe . + +# Для Mac с Apple Silicon +GOOS=darwin GOARCH=arm64 go build -o app-mac . + +# Для Raspberry Pi +GOOS=linux GOARCH=arm GOARM=7 go build -o app-rpi . +``` + +Никаких дополнительных компиляторов, тулчейнов, виртуальных машин. Go всё умеет из коробки. + +Хотите увидеть все поддерживаемые платформы? Их больше 45: + +```bash +go tool dist list +aix/ppc64 +android/amd64 +darwin/amd64 +darwin/arm64 +js/wasm +linux/amd64 +linux/arm64 +wasip1/wasm +windows/amd64 +... и ещё 40+ +``` + +### `CGO_ENABLED=0` — полностью статический бинарник + +Мы говорили, что Go-бинарник самодостаточный — внутри всё необходимое. Но это не совсем так. По умолчанию Go в некоторых случаях использует **C-библиотеки** операционной системы — например, для работы с DNS (преобразование доменных имён в IP-адреса) и для получения информации о пользователях системы. Это называется **CGO** (C-Go) — мост между Go и кодом на языке C. + +Проблема в том, что C-библиотеки разные на разных системах. Бинарник, собранный с CGO, ожидает определённую C-библиотеку на целевой системе. Если её там нет или стоит другая версия — бинарник упадёт с непонятной ошибкой. + +Чтобы бинарник был **по-настоящему** самодостаточным и не зависел ни от чего на целевой системе, отключаем CGO: + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app . +``` + +`CGO_ENABLED=0` говорит Go: "не используй C-библиотеки, сделай всё сам". Go заменит системные вызовы своими реализациями на чистом Go. Результат — бинарник, которому нужна только операционная система. Никаких библиотек, никаких зависимостей. + +:::danger Это важно для Docker +Популярный базовый образ Alpine Linux использует `musl libc` вместо стандартной `glibc`. Если собрать бинарник без `CGO_ENABLED=0` — в Alpine он упадёт с загадочной ошибкой `no such file or directory`, хотя файл на месте. Система не может найти C-библиотеку, которую бинарник ожидает. Правило простое: **для Docker-сборок всегда ставьте `CGO_ENABLED=0`**. +::: + +--- + +## Что под капотом: почему Go компилируется за секунды + +В первом уроке мы говорили, что Go создали, потому что создатели устали ждать компиляцию C++ по 45 минут. Вот как они решили эту проблему. + +### Этапы компиляции (на пальцах) + +Когда вы набираете `go build`, происходит следующее: + +![Пайплайн компиляции Go](/images/go-compilation-pipeline.png) + +1. **Lexer (Scanner)** — разбивает текст на токены: ключевые слова, имена, числа, операторы +2. **Parser** — собирает токены в дерево (AST — Abstract Syntax Tree), отражающее структуру программы +3. **Semantic Analysis (Type Checker)** — проверяет типы: "x — это int, нельзя сложить со строкой" +4. **Intermediate Representation** — преобразует AST в промежуточное представление (IR) +5. **SSA (Static Single Assignment)** — оптимизирует IR: удаляет мёртвый код, сворачивает константы +6. **Machine Code Generation** — превращает SSA в инструкции для конкретного процессора +7. **Linker** — склеивает машинный код с runtime → готовый бинарник +8. **Execution** — ОС загружает бинарник и запускает + +Вы не обязаны понимать каждый шаг. Важно знать, что компиляция — это **перевод**: человеческий текст → машинные инструкции. Go делает этот перевод очень быстро. + +:::tip Для опытных: что происходит на каждом этапе +**SSA (Static Single Assignment)** — ключевое промежуточное представление. Каждой переменной присваивается значение ровно один раз: `x = 1; x = x + 2` превращается в `x₁ = 1; x₂ = x₁ + 2`. Это упрощает оптимизации: удаление мёртвого кода, свёртку констант, устранение лишних проверок границ массивов. + +**Escape analysis** — компилятор решает, где жить переменной: на стеке (быстро, бесплатная очистка) или в куче (дороже, нагружает GC — Garbage Collector, сборщик мусора, который автоматически освобождает неиспользуемую память). Если ссылка на переменную "утекает" за пределы функции — куча. Иначе — стек. Посмотреть решения компилятора: `go build -gcflags="-m" .` + +**Inlining** — компилятор подставляет тело маленьких функций прямо в место вызова, убирая накладные расходы. Go инлайнит функции до определённой "стоимости" (80 узлов AST). С Go 1.22 инлайнер стал агрессивнее. +::: + +:::tip Для опытных: что происходит до `main()` +Когда ОС запускает Go-бинарник, до вашего `main()` далеко: + +1. **OS Loader** загружает файл в память, находит точку входа +2. **Ассемблерный bootstrap** (`_rt0_amd64_linux` и подобные) сохраняет argc/argv +3. **`runtime.rt0_go`** выделяет стек для системной горутины g0, инициализирует кучу +4. **Запуск подсистем** — GC, планировщик горутин, сетевой поллер +5. **`init()` функции** всех импортированных пакетов — снизу вверх по дереву зависимостей +6. **`main.main()`** — наконец-то ваш код + +Именно поэтому даже пустая Go-программа "тяжелее" аналога на C — она несёт с собой полноценную среду выполнения. +::: + +### Почему быстрее C++ и Rust + +**Главная причина — модель зависимостей.** Роб Пайк измерял: при компиляции Go-кода компилятор читает в **40 раз** меньше исходного текста, чем при компиляции C++. В C++ каждый `#include ` заново "объясняет" компилятору, что такое строки. В Go информация о пакете хранится в скомпилированном виде — компилятор читает только прямые импорты, не проваливаясь в их зависимости. + +**Другие факторы:** +- **25 ключевых слов** — парсер работает мгновенно +- **Запрет циклических импортов** — граф зависимостей можно компилировать параллельно +- **Неиспользуемый импорт = ошибка** — компилятор не тратит время на мёртвый код +- **Нет шаблонов как в C++** — не нужно раздувать код при инстанциации + +Для масштаба: проект Istio (платформа для управления микросервисами, ~350 000 строк Go) компилируется с нуля за **33 секунды** на мощной машине. С прогретым кэшем — меньше секунды. + +:::tip Для любопытных +`go build -x` покажет каждую команду, которую выполняет тулчейн: компиляцию каждого пакета, генерацию конфигов, линковку. Десятки строк вывода — и всё это за пару секунд. +::: + +--- + +## Практические сценарии + +### Docker multi-stage build + +Стандартный способ доставки Go-сервиса на сервер — многоэтапная сборка Docker: + +```dockerfile +# Этап 1: собираем бинарник +FROM golang:1.25 AS builder +WORKDIR /app +COPY go.mod go.sum ./ # go.sum — файл с контрольными суммами зависимостей +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -trimpath -o server . + +# Этап 2: минимальный образ +FROM scratch +COPY --from=builder /app/server /server +ENTRYPOINT ["/server"] +``` + +Образ `scratch` — абсолютно пустой. Ни bash, ни curl, ни libc. Только ваш бинарник. Финальный образ: **3–5 МБ** вместо 700 МБ+ с полным SDK (Software Development Kit — набор инструментов для разработки). Это возможно именно потому, что Go-бинарник самодостаточный. + +### Makefile + +Makefile — это файл с набором команд-рецептов. Вместо того чтобы каждый раз набирать длинную команду сборки с десятком флагов, вы описываете её один раз в Makefile и потом вызываете коротким `make build`. Утилита `make` есть на Linux и macOS из коробки, для Windows можно установить отдельно. + +Для проектов побольше удобно завернуть команды в Makefile: + +```makefile +APP_NAME = myapp +.PHONY: run build test clean cross + +run: + go run . +build: + go build -ldflags="-s -w" -trimpath -o $(APP_NAME) . +test: + go test -race ./... +clean: + rm -f $(APP_NAME) $(APP_NAME)-* +cross: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(APP_NAME)-linux . + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o $(APP_NAME)-mac . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o $(APP_NAME).exe . +``` + +Теперь `make build` вместо длинной команды с флагами. `make cross` — сборка под три ОС разом. + +:::tip Для опытных: GoReleaser +Когда проект дорастёт до публичных релизов — кросс-компиляция, архивы, changelog, публикация на GitHub/GitLab, конфиги для Homebrew и Scoop — вручную это ад. **GoReleaser** автоматизирует весь цикл. Один файл `.goreleaser.yaml`, одна команда — и релиз под десятки платформ готов. Его используют Kubernetes, Docker, GitHub CLI и тысячи open-source проектов. +::: + +--- + +## Типичные грабли новичков + +### 1. "undefined" при `go run main.go` + +Когда проект подрастёт, код естественно разобьётся на несколько файлов — например, `main.go` и `math.go` в одном пакете `main`. И тут есть нюанс. + +Когда вы явно указываете файлы — `go run main.go` — Go считает только перечисленные файлы частью вашего пакета. Остальные `.go`-файлы в той же директории он не видит, как будто их нет: + +```bash +go run main.go +./main.go:10:2: undefined: Add # функция Add из math.go — "не существует" + +go run . # ✅ все файлы пакета включены +``` + +Важно не путать две вещи: +- **Соседние файлы того же пакета** (например `math.go` с `package main` в той же папке) — при `go run main.go` **не подтягиваются**. Это и есть проблема. +- **Импортированные пакеты** (через `import`) — подтягиваются нормально. Если у вас `import "myproject/utils"`, пакет `utils` скомпилируется, потому что он разрешается через модульную систему, а не через "соседние файлы в директории". + +`go run .` и `go build .` включают **все `.go`-файлы** в директории (кроме `_test.go`). Поэтому привыкайте к `go run .` — с ней таких проблем не будет. + +### 2. "cannot run non-main package" + +```bash +go run . +go run: cannot run non-main package +``` + +Забыли написать `package main` или нет функции `main()`. Помните из прошлого урока: только `package main` + `func main()` создают исполняемую программу. + +### 3. Бинарник не запускается на другой ОС + +Собрали на Mac, скопировали на Linux-сервер: + +```bash +./myapp +bash: ./myapp: cannot execute binary file: Exec format error +``` + +Это бинарник для macOS, а запускаете на Linux. Нужна кросс-компиляция: + +```bash +GOOS=linux GOARCH=amd64 go build -o myapp . +``` + +### 4. "permission denied" (Linux/macOS) + +```bash +./myapp +bash: ./myapp: Permission denied +``` + +Нет прав на выполнение. `go build` ставит их автоматически, но при копировании через архив или сеть бит выполнения может потеряться. На Windows такой проблемы нет — там работает по-другому. + +```bash +chmod +x myapp +``` + +### 5. Загадочная "no such file or directory" в Docker + +```bash +exec /server: no such file or directory +``` + +Файл на месте, но система не может найти динамический загрузчик `glibc`. Собрали без `CGO_ENABLED=0`, а запускаете в Alpine с `musl`. + +```bash +# Правильная сборка для Docker: +CGO_ENABLED=0 go build -o server . +``` + +--- + +## Полный пример: от кода до бинарника + +Возьмём программу greeter из прошлого урока и пройдём полный цикл: + +```go +// main.go +package main + +import ( + "fmt" + "os" + "strings" +) + +var version = "dev" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Println(version) + return + } + + name := "Мир" + if len(os.Args) > 1 { + name = strings.Join(os.Args[1:], " ") + } + fmt.Printf("Привет, %s!\n", name) +} +``` + +```bash +# 1. Быстрый запуск во время разработки +go run . Вася +Привет, Вася! + +# 2. Собираем бинарник с версией +go build -ldflags="-s -w -X main.version=1.0.0" -trimpath -o greeter . + +# 3. Проверяем +./greeter --version +1.0.0 +./greeter дорогой друг +Привет, дорогой друг! + +# 4. Кросс-компиляция для Linux-сервера +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -ldflags="-s -w -X main.version=1.0.0" -trimpath -o greeter-linux . + +# 5. Оба файла — около 1.3 МБ каждый +``` + +Один исходник — бинарники под любую платформу. Без Docker, без виртуальных машин, без боли. + +--- ## Итоги -TODO +| Команда / Концепция | Что помнить | +|---------------------|-------------| +| `go run .` | Компилирует и запускает. Бинарник временный. Для разработки. | +| `go build -o app .` | Создаёт постоянный бинарник. Для деплоя и CI/CD. | +| `go install` | Ставит бинарник в `GOPATH/bin`. Для CLI-утилит. | +| `-ldflags="-s -w"` | Убирает отладочную инфу. Минус 25–30% размера. | +| `-trimpath` | Убирает пути. Безопасность + воспроизводимость. | +| `-X main.var=val` | Вшивает значение переменной при сборке. | +| `GOOS` / `GOARCH` | Кросс-компиляция. Любая ОС, любая архитектура. | +| `CGO_ENABLED=0` | Полностью статический бинарник. Обязателен для Docker. | +| `go build -x` | Показывает все шаги компиляции. Для любопытных. | + +--- + +## Задачи + +### Задача 1: Заглядываем под капот ⭐ + +Запустите программу Hello World так, чтобы увидеть путь к временной директории сборки. Временные файлы не должны удаляться после запуска. + +
+Решение + +```bash +go run -work main.go +WORK=/var/folders/.../go-build1234567890 # путь зависит от ОС +Привет, мир! +``` + +Флаг `-work` печатает путь и сохраняет временную директорию. Можно зайти в неё и найти скомпилированный бинарник. + +
+ +### Задача 2: Оптимальная сборка ⭐⭐ + +Соберите бинарник для Linux ARM64 с минимальным размером, без путей файловой системы и без зависимостей от C-библиотек. Имя файла — `server`. + +
+Решение + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ + go build -ldflags="-s -w" -trimpath -o server . +``` + +- `CGO_ENABLED=0` — статический бинарник без C-зависимостей +- `GOOS=linux GOARCH=arm64` — целевая платформа +- `-ldflags="-s -w"` — убираем символы и DWARF +- `-trimpath` — убираем абсолютные пути + +
+ +### Задача 3: Версия из командной строки ⭐⭐⭐ + +Напишите программу, которая при запуске с флагом `--version` выводит номер версии, а без флага — приветствие. Версия должна вшиваться при сборке через `-ldflags`, а не хардкодиться. + +```bash +go build -ldflags="-X main.version=2.1.0" -o app . +./app --version +app v2.1.0 +./app +Привет из app! +``` + +
+Подсказка + +Объявите `var version = "dev"` и используйте `os.Args` для проверки аргументов. + +
+ +
+Решение + +```go +package main + +import ( + "fmt" + "os" +) + +var version = "dev" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Println("app v" + version) + return + } + fmt.Println("Привет из app!") +} +``` + +Сборка: + +```bash +go build -ldflags="-X main.version=2.1.0" -o app . +``` + +
+ +### Задача 4: Мультиплатформенная сборка ⭐⭐⭐ + +Напишите скрипт (или команды), который соберёт один и тот же проект под три платформы: Linux amd64, macOS arm64, Windows amd64. Все бинарники должны быть статическими, минимального размера и лежать в папке `dist/`. + +
+Решение + +```bash +mkdir -p dist + +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app-linux . +CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o dist/app-mac . +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app.exe . +``` + +Или через Makefile: + +```makefile +.PHONY: dist +dist: + mkdir -p dist + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app-linux . + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o dist/app-mac . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app.exe . +``` + +
+ +--- + +## Что дальше? + +Теперь вы умеете превращать код в бинарник, собирать под любую платформу и понимаете, что происходит за кулисами компиляции. В следующем уроке разберём **Go Playground** — веб-среду для запуска Go-кода прямо в браузере: быстрые эксперименты, обмен сниппетами с коллегами и тестирование идей без локальной установки. + +--- + +## Источники + +- [Go Command Documentation](https://pkg.go.dev/cmd/go) — официальная документация команды `go` +- [Go at Google: Language Design in the Service of Software Engineering](https://go.dev/talks/2012/splash.article) — Роб Пайк о дизайне Go +- [Go 1.24 Release Notes](https://go.dev/doc/go1.24) — кэширование `go run`, директива `tool` +- [Go 1.25 Release Notes](https://go.dev/doc/go1.25) — DWARF v5, автоматический GOMAXPROCS +- [How to Reduce Go Binary Size](https://words.filippo.io/shrink-your-go-binaries-with-this-one-weird-trick/) — Filippo Valsorda об оптимизации размера +- [Effective Go](https://go.dev/doc/effective_go) — рекомендации по идиоматичному Go +- [Understanding Go Compiler](https://www.linkedin.com/pulse/understanding-go-compiler-kanishka-naik-sbmwc) — Kanishka Naik о пайплайне компиляции Go From e77225a363854c0c96e45eb0eaa4c918999c0d02 Mon Sep 17 00:00:00 2001 From: Alex B Date: Fri, 20 Feb 2026 00:45:59 +0200 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20add=20English=20translation=20of=20?= =?UTF-8?q?lesson=201.6=20=E2=80=94=20compile=20and=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/content/docs/en/compile-and-run.md | 835 ++++++++++++++++++ .../src/content/docs/en/program-structure.md | 5 +- apps/web/src/data/sidebar-unified.json | 3 + apps/web/src/data/sidebar.json | 4 + apps/web/src/data/toc.json | 5 + .../content/01-intro/06-compile-and-run.md | 819 ++++++++++++++++- 6 files changed, 1663 insertions(+), 8 deletions(-) create mode 100644 apps/web/src/content/docs/en/compile-and-run.md diff --git a/apps/web/src/content/docs/en/compile-and-run.md b/apps/web/src/content/docs/en/compile-and-run.md new file mode 100644 index 0000000..7ead96e --- /dev/null +++ b/apps/web/src/content/docs/en/compile-and-run.md @@ -0,0 +1,835 @@ +--- +title: Compile and Run +description: 'go build, go run and executables' +tableOfContents: + minHeadingLevel: 2 + maxHeadingLevel: 3 +author: godojo +authorName: Godojo Master +updatedAt: '2026-02-20' +readingTime: 29 +--- +In the previous lesson we dissected what makes up a Go programme. By now you've typed `go run main.go` a dozen times and seen the output. But what's behind that command? Where does the compiled file go? And how do you turn code into a **binary** — an executable file containing machine instructions that you can simply copy onto a server and run — no Go, no dependencies, no nothing? + +In the first lesson we mentioned "compiled language" and "single binary with no dependencies" — time to see what those words mean in practice. Compilation is the translation of your code into the language of the processor: ones and zeroes that the CPU executes directly. The result of that translation is the binary (from the word binary — base-two). Unlike Python, where an interpreter reads and executes your programme's text line by line every time, Go does the translation once — and from that point the processor works with the finished result. That's where the speed comes from. + +Today we pop the bonnet. + +--- + +For all examples in this lesson we'll use our trusty `main.go`: + +```go +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} +``` + +Open the terminal in VS Code (`` Ctrl+` ``) and make sure you're in the folder with this file. + +--- + +## `go run` — The Illusion of an Interpreter + +If you've come from Python or JavaScript, `go run` feels familiar: type a command — programme runs. No intermediate files, no faffing about. Feels like Go just executes the text. + +But Go is a compiled language. Always. Even when `go run` pretends to be an interpreter, a full-blown build is happening under the bonnet: + +1. A temporary directory is created (the path depends on your OS) +2. The source code is compiled into a native binary +3. The binary is launched as a separate process +4. Once the programme finishes, the temporary directory is deleted + +That's why nothing appears in your working folder — everything lives and dies in a temporary directory. + +### Peeking Under the Bonnet + +Want to see what's going on? Add the `-x` flag: + +```bash +go run -x main.go +``` + +Dozens of lines will scroll through the terminal — every command Go executes behind the scenes. You'll see the path to the temporary directory (`WORK=...`), compiler invocations, and linking. + +If you want to keep the temporary files for inspection, there's the `-work` flag: + +```bash +go run -work main.go +WORK=/var/folders/.../go-build3712456890 # path depends on your OS +Hello, World! +``` + +The directory won't be deleted after the programme finishes. You can go in there and find the actual compiled binary. + +:::tip Handy trick +`go run -work` is a great way to prove to yourself that Go really does produce a proper executable, rather than "interpreting" your code. +::: + +### Passing Arguments + +In the previous lesson we wrote a greeter programme with `os.Args`. Running it looked like this: + +```bash +go run main.go Alice +Hello, Alice! +``` + +Everything after the filename is arguments to your programme. Go sorts out where its own flags end and your data begins. Go flags go **before** the file, programme arguments go **after**: + +```bash +go run -race main.go --port 8080 +# ^^^^ ^^^^^^^^^^ +# Go flag your programme's arguments +``` + +By the way, instead of `go run main.go` you can write **`go run .`** — the dot means "the entire package in the current directory". Whilst you've only got one file there's no difference, but it's the more idiomatic* approach, and later you'll see why. + +:::caution The dot only works with a Go module +Commands using `.` (`go run .`, `go build .`) require a `go.mod` file in your project directory. If there isn't one, Go will throw an error. Create a module with a single command: + +```bash +go mod init hello +``` + +`hello` is the name of your module (can be anything). After this, a `go.mod` file will appear in the directory. We'll talk about modules properly in a separate lesson; for now, just know: **run `go mod init` before using `.` for the first time**. +::: + +* **Idiomatic** means "the way the community does it". Every language has unwritten rules: not just "it works", but "this is how experienced developers write it". In the Go world, `go run .` is idiomatic; `go run main.go` isn't. You'll come across this word often in Go documentation and articles. + +### Limitations + +Since **Go 1.24**, `go run` has caching: if the code hasn't changed, a repeat run grabs the ready-made binary from cache. But there are still plenty of limitations: + +- **Slower than running the binary directly.** Even with caching, `go run` checks whether the build is up to date on every invocation. For large projects the overhead can be significant — in some cases up to 8 times slower than running the compiled binary. +- **No control over the binary.** It sits somewhere in the cache; you can't hand it to someone or ship it to a server. `os.Executable()` returns a path into the temporary directory — if your programme relies on its own path, things will break. +- **Cache is short-lived.** Go aggressively clears cached binaries — roughly after 2 days of non-use. Come morning, `go run` might recompile from scratch. +- **`package main` only.** You can't run a library package — only executable programmes. +- **Cross-compilation is pointless.** `GOOS=linux go run .` doesn't make sense — the binary runs on your machine, not on the target platform. +- **Not for production.** Production is the environment where your programme runs "for real": serving actual users, running on a server round the clock. The opposite is the development environment, where you write and test code on your own machine. `go run` is a development tool, not a deployment method. + +**Bottom line:** `go run` is for a quick look. For everything else, there's `go build`. + +--- + +## `go build` — Creating a Proper Binary + +When you need a file you can send to a colleague, upload to a server, or stick in a Docker container (Docker is a system for packaging and running applications in isolated environments — we'll get to know it later) — reach for `go build`. + +```bash +go build -o myapp . +./myapp +Hello, World! +``` + +A file called `myapp` has appeared in the directory (on Windows it'd be `myapp.exe`), about 2 MB or a touch more. It's a self-contained binary. The target machine doesn't need Go, doesn't need libraries, doesn't need a runtime. Just copy the file and run it. + +### Output File Name + +Without the `-o` flag, Go picks the name itself: + +```bash +go build . # Name from go.mod (or the directory name) +go build -o server . # Explicitly set a name +go build -o bin/app . # You can specify a path too +``` + +On Windows, `.exe` is added automatically. + +:::tip `go build` without `package main` +If you run `go build .` in a directory with a library package (not `main`), Go will compile the code and check it for errors but **won't create an output file**. A handy way to validate code without cluttering the directory — especially in CI. +::: + +### The `-race` Flag — Data Race Detector + +Imagine two people editing the same document at the same time, unable to see each other. One writes a heading, the other deletes it — the result is unpredictable. In programming this is called a **data race** — when several parts of a programme simultaneously read and modify the same variable. The outcome depends on who got there first, and each run can produce a different result. + +Go can find these situations automatically. Here's an example — don't try to parse every line just yet; we'll cover the `go` keyword in the concurrency lesson. What matters now is the principle: + +```go +package main + +import "fmt" + +func main() { + count := 0 + for i := 0; i < 1000; i++ { + go func() { // launch 1000 parallel tasks + count++ // all writing to the same variable + }() + } + fmt.Println(count) +} +``` + +Run without the flag — the programme silently produces an unpredictable result: + +```bash +go run . +0 # or 127, or 999 — different every time +``` + +Now with `-race`: + +```bash +go run -race main.go +================== +WARNING: DATA RACE +Read at 0x00c00011c028 by goroutine 9: + main.main.func1() + /home/user/main.go:9 +0x2e + +Previous write at 0x00c00011c028 by goroutine 8: + main.main.func1() + /home/user/main.go:9 +0x44 + +Goroutine 9 (running) created at: + main.main() + /home/user/main.go:8 +0x4a + +Goroutine 8 (finished) created at: + main.main() + /home/user/main.go:8 +0x4a +================== +... 2 more similar warnings ... +651 +Found 3 data race(s) +exit status 66 +``` + +Go found **3 races** and shows precisely: line 9, variable `count` (address `0x00c00011c028`), several parallel tasks writing to it simultaneously. The programme exited with code 66 — a special error code for races. The number `651` instead of the expected `1000` is the result of lost updates, a classic consequence of a data race. Without `-race` the programme would silently give wrong results — a bug that's extremely hard to catch by hand. + +The `-race` flag works with `go build`, `go run`, and `go test`. A binary built with it is slower and uses more memory, so you don't ship it to production. But in tests and during development — it's standard practice. + +### Why Does Hello World Weigh 2 Megabytes? + +After their first `go build`, beginners are surprised: a five-line programme — 2 MB? In C the equivalent is 16 KB. + +The thing is, a Go binary isn't just your code. It's an entire universe: + +- **Go runtime** — the execution environment +- **Garbage Collector** — automatic memory management +- **Goroutine Scheduler** — the goroutine scheduler (remember from the first lesson — multitasking out of the box?) +- **Symbol table and debug information** — for panic traces and debugging +- Parts of the standard library that you imported + +A single `import "fmt"` pulls in reflection*, I/O*, and string formatting*. All baked in. + +- * **Reflection** — a mechanism that allows a programme to analyse and modify its own structure at runtime: discover variable types, read their values, call functions by name. In formal terms, it's the ability of a programme to examine its own type system whilst running. In plainer terms — the ability of a programme to "look at itself". Think about it: you write `fmt.Println(42)` and get `42`. You write `fmt.Println("hello")` and get `hello`. You write `fmt.Println(3.14)` and get `3.14`. How does `Println` know how to print each of these values when a number and a string are completely different things? Through reflection: at the moment of the call, the function asks the runtime "what was I given — a number? a string? something else?" — and based on the answer, chooses how to display it. Without reflection you'd have to write a separate function for every data type. +- * **I/O** (Input/Output) — everything to do with reading and writing: output to the terminal, reading files, sending data over the network. `fmt.Println` writes text to standard output (stdout) — that's I/O. +- * **String formatting** — turning data into readable text. When you write `fmt.Println("Answer:", 42)`, Go converts the number `42` into the string `"42"` and glues it to `"Answer:"`. + +This is why in the first lesson we said "single binary with no dependencies" — now you see what that means. Go puts everything it needs right into the file. + +### Reducing the Size + +Why bother making the binary smaller? A smaller binary means faster downloads to servers (especially if you've got dozens of them), less space in the Docker image, faster container starts. For Hello World the difference is negligible, but when a project grows to 20–50 MB, shaving off 25–30% starts to matter. + +```bash +# Standard build +go build -o app . +# app — ~2 MB + +# Without debug information (-s: symbols, -w: DWARF) +go build -ldflags="-s -w" -o app . +# app — ~1.5 MB +``` + +The `-s -w` flags strip the symbol table and DWARF debug information. In practice that's **minus 25–30%** of the size. Panic traces still work — Go keeps its own internal table for error tracking separately. + +:::tip Production recipe +```bash +go build -ldflags="-s -w" -trimpath -o app . +``` +The `-trimpath` flag additionally strips absolute filesystem paths from the binary. A bonus for security and build reproducibility. +::: + +### Embedding the Version + +Picture this: you've shipped a binary to a server. A month later something breaks. Which version of the code is running there — you can't remember. You've rebuilt ten times since then. If the binary can answer "I'm version 1.0.0" on its own — the problem is solved in a second: run it with `--version`, compare with the current release, work out whether you need to update. + +That's exactly why the version is baked into the binary at build time: + +```go +package main + +import "fmt" + +var version = "dev" + +func main() { + fmt.Println("Version:", version) +} +``` + +In the code, `version` is `"dev"` — the default value for development. But at build time we swap it out: + +```bash +go build -ldflags="-X main.version=1.0.0" -o app . +./app +Version: 1.0.0 +``` + +The `-X` flag replaces the value of a string variable at compile time — no need to change the source code. In CI/CD systems (Continuous Integration / Continuous Delivery — automated building and shipping of code) this is standard practice: they embed the version number, build date, and Git commit hash. Any binary can "introduce itself". + +:::tip From Go 1.24 onwards +Go automatically embeds Git information (commit hash, version tag, a `+dirty` marker if there are unsaved changes). You can view it with `go version -m ./app`. +::: + +### Build Cache — Why the Second Time Is Faster + +A **cache** is a place where results of previous work are stored so you don't have to redo it. Your browser caches images from websites so it doesn't download them again. Go does the same with compilation: it saves already-compiled packages, and on the next build only recompiles those that changed. + +```bash +go build -o app . # First time: ~2 seconds +go build -o app . # Second time: ~0.3 seconds (linking only) +``` + +To find out where exactly Go keeps the cache on your machine: + +```bash +go env GOCACHE +``` + +The path depends on your OS — it's different on Linux, macOS, and Windows. If something's gone wrong and you want to clear the cache — `go clean -cache`. + +:::tip For the experienced: PGO — Profile-Guided Optimisation +Since Go 1.22, Profile-Guided Optimisation has been stable. The idea: you capture a CPU profile from a running application (via `pprof`), drop the `default.pgo` file in the project root, and on the next build the compiler uses real data about hot paths for aggressive inlining and optimisation. Performance gain: **2–14%** without changing a single line of code. More in the lessons on testing and profiling. +::: + +--- + +## `go install` — Installing Tools Globally + +`go build` creates a binary in the current folder — wherever you happen to be in the terminal. That's handy for your project, but what if you want to install **someone else's tool** — a linter, formatter, code generator — and use it from anywhere on the system? + +That's what `go install` is for. It does the same as `go build`, but places the finished binary not next to you, but in a special folder. In the installation lesson we saw the [`GOBIN`](/en/installation/#setting-up-environment-variables) variable in a table — this is it. To find out where exactly Go puts tools on your system: + +```bash +go env GOPATH +``` + +Binaries end up in the `bin` subfolder of that path. If you added that folder to `PATH` when installing Go, the tools will be available from any directory. If not — now's the time to do it (details in the installation lesson). + +Example — let's install `goimports`, a tool for automatically sorting imports: + +```bash +go install golang.org/x/tools/cmd/goimports@latest +``` + +This command downloads the `goimports` source code from `golang.org/x/tools`, compiles it, and puts the finished binary in that `bin` folder. The `@latest` suffix means "latest version". The tool is now available globally: + +```bash +goimports -w main.go +``` + +This works from any folder — provided the `bin` folder is in your `PATH`. This is exactly how the tools in the editor lesson were installed — `gopls`, `dlv`, and other VS Code extensions were installed via `go install`. + +### Difference from `go build` + +| | `go build` | `go install` | +|---|---|---| +| **Where the binary goes** | Current folder | The `bin` folder inside `GOPATH` | +| **What for** | Your project | Third-party tools | +| **Available globally** | No | Yes (if `bin` is in `PATH`) | +| **Typical usage** | `go build -o server .` | `go install tool@latest` | + +:::tip For the experienced: the `tool` directive in go.mod (Go 1.24) +Previously, to pin a tool's version (linter, code generator) across a project, developers used a hack — a `tools.go` file with blank imports. Since Go 1.24, there's a `tool` directive right in `go.mod`: + +```bash +go get -tool github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +This adds a `tool` line to `go.mod`, and now any team member runs the tool via `go tool golangci-lint run ./...` — guaranteed to be the same version as everyone else. No more discrepancies between developers and CI. +::: + +### When to Use What + +| | `go run` | `go build` | `go install` | +|---|----------|------------|--------------| +| **What it does** | Compiles → runs → deletes | Compiles → saves | Compiles → puts in `GOPATH/bin` | +| **File remains** | ❌ No | ✅ In current folder | ✅ In `GOPATH/bin` | +| **When to use** | Development, experiments | Building for server, Docker | Installing CLI tools | +| **Caching** | Since Go 1.24 | Intermediate files | Intermediate files | + +**Typical workflow:** +1. `go run .` — whilst you're writing and debugging +2. `go build` — when you need the final artefact +3. `go install` — for tools you use regularly + +--- + +## Cross-Compilation — Building for Any OS + +An important point: every binary is built **for a specific OS and architecture**. A binary built on macOS won't run on Linux — you'll get an `Exec format error`. And vice versa. This isn't like a Python script that runs the same everywhere there's an interpreter. A binary contains machine instructions for a specific platform. + +If you develop on macOS but your server runs Linux, you need to build the binary specifically for Linux. This is **cross-compilation**: building on one platform for another. In the first lesson we mentioned "built for Linux on a Mac with a single command" — time to show how. + +Remember the code from the installation lesson? + +```go +fmt.Printf("OS: %s\n", runtime.GOOS) +fmt.Printf("Arch: %s\n", runtime.GOARCH) +``` + +Back then, `runtime.GOOS` showed your current OS — and that makes sense, since you were building and running on the same machine. But `runtime.GOOS` and `runtime.GOARCH` aren't "detecting the system at run time". They're constants baked into the binary at compile time. By default Go sets them to your OS and architecture, so everything matches. But you can override them with two environment variables — and then the binary will be built for a **different** platform: + +```bash +# Build on Mac, run on a Linux server +GOOS=linux GOARCH=amd64 go build -o app-linux . + +# For Windows +GOOS=windows GOARCH=amd64 go build -o app.exe . + +# For Mac with Apple Silicon +GOOS=darwin GOARCH=arm64 go build -o app-mac . + +# For Raspberry Pi +GOOS=linux GOARCH=arm GOARM=7 go build -o app-rpi . +``` + +No extra compilers, toolchains, or virtual machines. Go does it all out of the box. + +Want to see every supported platform? There are over 45: + +```bash +go tool dist list +aix/ppc64 +android/amd64 +darwin/amd64 +darwin/arm64 +js/wasm +linux/amd64 +linux/arm64 +wasip1/wasm +windows/amd64 +... and 40+ more +``` + +### `CGO_ENABLED=0` — A Truly Static Binary + +We said the Go binary is self-contained — everything it needs is inside. But that's not quite the full picture. By default, Go sometimes uses **C libraries** from the operating system — for instance, for DNS resolution (translating domain names into IP addresses) and for looking up system user information. This is called **CGO** (C-Go) — a bridge between Go and C code. + +The problem is that C libraries differ across systems. A binary built with CGO expects a particular C library on the target system. If it's not there or it's a different version, the binary crashes with a cryptic error. + +To make the binary **truly** self-contained, with no dependencies on anything on the target system, disable CGO: + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app . +``` + +`CGO_ENABLED=0` tells Go: "don't use C libraries, do everything yourself". Go will replace system calls with its own pure-Go implementations. The result is a binary that needs nothing but the operating system. No libraries, no dependencies. + +:::danger This matters for Docker +The popular Alpine Linux base image uses `musl libc` instead of the standard `glibc`. If you build a binary without `CGO_ENABLED=0`, it'll crash in Alpine with the baffling error `no such file or directory` — even though the file is right there. The system can't find the C library the binary expects. Simple rule: **for Docker builds, always set `CGO_ENABLED=0`**. +::: + +--- + +## Under the Bonnet: Why Go Compiles in Seconds + +In the first lesson we said that Go was created because its creators were fed up waiting 45 minutes for C++ to compile. Here's how they solved the problem. + +### Compilation Stages (On the Back of a Napkin) + +When you type `go build`, here's what happens: + +![Go compilation pipeline](/images/go-compilation-pipeline.png) + +1. **Lexer (Scanner)** — splits the text into tokens: keywords, names, numbers, operators +2. **Parser** — assembles tokens into a tree (AST — Abstract Syntax Tree) reflecting the programme's structure +3. **Semantic Analysis (Type Checker)** — verifies types: "x is an int, you can't add it to a string" +4. **Intermediate Representation** — converts the AST into an intermediate representation (IR) +5. **SSA (Static Single Assignment)** — optimises the IR: removes dead code, folds constants +6. **Machine Code Generation** — turns SSA into instructions for the target processor +7. **Linker** — stitches machine code together with the runtime → finished binary +8. **Execution** — the OS loads the binary and runs it + +You don't have to understand every step. The key thing to know is that compilation is a **translation**: human text → machine instructions. Go does this translation very fast. + +:::tip For the experienced: what happens at each stage +**SSA (Static Single Assignment)** is the key intermediate representation. Each variable is assigned a value exactly once: `x = 1; x = x + 2` becomes `x₁ = 1; x₂ = x₁ + 2`. This simplifies optimisations: dead code elimination, constant folding, removing unnecessary array bounds checks. + +**Escape analysis** — the compiler decides where a variable lives: on the stack (fast, free cleanup) or on the heap (costlier, loads the GC — Garbage Collector, the system that automatically frees unused memory). If a reference to a variable "escapes" beyond the function — heap. Otherwise — stack. To see the compiler's decisions: `go build -gcflags="-m" .` + +**Inlining** — the compiler substitutes the body of small functions directly at the call site, eliminating call overhead. Go inlines functions up to a certain "cost" (80 AST nodes). Since Go 1.22, the inliner has become more aggressive. +::: + +:::tip For the experienced: what happens before `main()` +When the OS launches a Go binary, your `main()` is still a long way off: + +1. **OS Loader** loads the file into memory, finds the entry point +2. **Assembly bootstrap** (`_rt0_amd64_linux` and friends) saves argc/argv +3. **`runtime.rt0_go`** allocates a stack for the system goroutine g0, initialises the heap +4. **Subsystem startup** — GC, goroutine scheduler, network poller +5. **`init()` functions** of all imported packages — bottom-up through the dependency tree +6. **`main.main()`** — your code, at last + +This is why even an empty Go programme is "heavier" than its C equivalent — it carries a full runtime environment with it. +::: + +### Why Faster Than C++ and Rust + +**The main reason is the dependency model.** Rob Pike measured it: when compiling Go code, the compiler reads **40 times** less source text than when compiling C++. In C++, every `#include ` re-explains to the compiler what strings are. In Go, package information is stored in compiled form — the compiler reads only direct imports without diving into their dependencies. + +**Other factors:** +- **25 keywords** — the parser works in a flash +- **No circular imports** — the dependency graph can be compiled in parallel +- **Unused import = error** — the compiler doesn't waste time on dead code +- **No C++-style templates** — no code bloat during instantiation + +For scale: the Istio project (a platform for managing microservices, ~350,000 lines of Go) compiles from scratch in **33 seconds** on a beefy machine. With a warm cache — under a second. + +:::tip For the curious +`go build -x` shows every command the toolchain executes: compiling each package, generating configs, linking. Dozens of output lines — and all of it in a couple of seconds. +::: + +--- + +## Practical Scenarios + +### Docker Multi-Stage Build + +The standard way to ship a Go service to a server — a multi-stage Docker build: + +```dockerfile +# Stage 1: build the binary +FROM golang:1.25 AS builder +WORKDIR /app +COPY go.mod go.sum ./ # go.sum — a file with dependency checksums +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -trimpath -o server . + +# Stage 2: minimal image +FROM scratch +COPY --from=builder /app/server /server +ENTRYPOINT ["/server"] +``` + +The `scratch` image is completely empty. No bash, no curl, no libc. Just your binary. Final image: **3–5 MB** instead of 700 MB+ with the full SDK (Software Development Kit — a set of development tools). This is possible precisely because the Go binary is self-contained. + +### Makefile + +A Makefile is a file containing a set of command recipes. Instead of typing out a long build command with a dozen flags every time, you describe it once in a Makefile and then call it with a short `make build`. The `make` utility comes pre-installed on Linux and macOS; for Windows it can be installed separately. + +For larger projects, wrapping commands in a Makefile is handy: + +```makefile +APP_NAME = myapp +.PHONY: run build test clean cross + +run: + go run . +build: + go build -ldflags="-s -w" -trimpath -o $(APP_NAME) . +test: + go test -race ./... +clean: + rm -f $(APP_NAME) $(APP_NAME)-* +cross: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(APP_NAME)-linux . + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o $(APP_NAME)-mac . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o $(APP_NAME).exe . +``` + +Now it's `make build` instead of a long command with flags. `make cross` — build for three OSes in one go. + +:::tip For the experienced: GoReleaser +When your project grows to the point of public releases — cross-compilation, archives, changelogs, publishing to GitHub/GitLab, Homebrew and Scoop configs — doing it by hand is a nightmare. **GoReleaser** automates the entire cycle. One `.goreleaser.yaml` file, one command — and a release for dozens of platforms is ready. It's used by Kubernetes, Docker, GitHub CLI, and thousands of open-source projects. +::: + +--- + +## Common Beginner Blunders + +### 1. "undefined" when running `go run main.go` + +As a project grows, code naturally splits into multiple files — say, `main.go` and `math.go` in the same `main` package. And here's the catch. + +When you explicitly list files — `go run main.go` — Go considers only the listed files part of your package. Other `.go` files in the same directory are invisible, as if they don't exist: + +```bash +go run main.go +./main.go:10:2: undefined: Add # the Add function from math.go — "doesn't exist" + +go run . # ✅ all package files included +``` + +It's important not to confuse two things: +- **Neighbouring files of the same package** (e.g. `math.go` with `package main` in the same folder) — with `go run main.go`, they **aren't picked up**. This is the problem. +- **Imported packages** (via `import`) — are picked up just fine. If you have `import "myproject/utils"`, the `utils` package will compile because it's resolved through the module system, not by scanning neighbouring files. + +`go run .` and `go build .` include **all `.go` files** in the directory (except `_test.go`). So get into the habit of `go run .` — you won't run into this problem. + +### 2. "cannot run non-main package" + +```bash +go run . +go run: cannot run non-main package +``` + +You forgot to write `package main` or there's no `main()` function. Remember from the previous lesson: only `package main` + `func main()` creates an executable programme. + +### 3. Binary doesn't run on another OS + +Built on Mac, copied to a Linux server: + +```bash +./myapp +bash: ./myapp: cannot execute binary file: Exec format error +``` + +That's a macOS binary, and you're trying to run it on Linux. You need cross-compilation: + +```bash +GOOS=linux GOARCH=amd64 go build -o myapp . +``` + +### 4. "permission denied" (Linux/macOS) + +```bash +./myapp +bash: ./myapp: Permission denied +``` + +No execute permission. `go build` sets it automatically, but when copying via an archive or over the network, the execute bit can get lost. On Windows this isn't a problem — it works differently there. + +```bash +chmod +x myapp +``` + +### 5. Mysterious "no such file or directory" in Docker + +```bash +exec /server: no such file or directory +``` + +The file's right there, but the system can't find the dynamic loader `glibc`. You built without `CGO_ENABLED=0` and you're running in Alpine with `musl`. + +```bash +# Correct build for Docker: +CGO_ENABLED=0 go build -o server . +``` + +--- + +## Full Example: From Code to Binary + +Let's take the greeter programme from the previous lesson and walk through the complete cycle: + +```go +// main.go +package main + +import ( + "fmt" + "os" + "strings" +) + +var version = "dev" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Println(version) + return + } + + name := "World" + if len(os.Args) > 1 { + name = strings.Join(os.Args[1:], " ") + } + fmt.Printf("Hello, %s!\n", name) +} +``` + +```bash +# 1. Quick run during development +go run . Alice +Hello, Alice! + +# 2. Build a binary with the version baked in +go build -ldflags="-s -w -X main.version=1.0.0" -trimpath -o greeter . + +# 3. Verify +./greeter --version +1.0.0 +./greeter dear friend +Hello, dear friend! + +# 4. Cross-compile for a Linux server +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -ldflags="-s -w -X main.version=1.0.0" -trimpath -o greeter-linux . + +# 5. Both files — about 1.3 MB each +``` + +One source file — binaries for any platform. No Docker, no virtual machines, no pain. + +--- + +## Summary + +| Command / Concept | What to Remember | +|---------------------|-------------| +| `go run .` | Compiles and runs. Binary is temporary. For development. | +| `go build -o app .` | Creates a permanent binary. For deployment and CI/CD. | +| `go install` | Puts a binary in `GOPATH/bin`. For CLI tools. | +| `-ldflags="-s -w"` | Strips debug info. Minus 25–30% size. | +| `-trimpath` | Strips paths. Security + reproducibility. | +| `-X main.var=val` | Embeds a variable value at build time. | +| `GOOS` / `GOARCH` | Cross-compilation. Any OS, any architecture. | +| `CGO_ENABLED=0` | Fully static binary. Required for Docker. | +| `go build -x` | Shows all compilation steps. For the curious. | + +--- + +## Exercises + +### Exercise 1: Peeking Under the Bonnet ⭐ + +Run the Hello World programme so that you see the path to the temporary build directory. The temporary files should not be deleted after the run. + +
+Solution + +```bash +go run -work main.go +WORK=/var/folders/.../go-build1234567890 # path depends on your OS +Hello, World! +``` + +The `-work` flag prints the path and preserves the temporary directory. You can go in and find the compiled binary. + +
+ +### Exercise 2: Optimal Build ⭐⭐ + +Build a binary for Linux ARM64 with minimal size, no filesystem paths, and no C library dependencies. Name the file `server`. + +
+Solution + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ + go build -ldflags="-s -w" -trimpath -o server . +``` + +- `CGO_ENABLED=0` — static binary with no C dependencies +- `GOOS=linux GOARCH=arm64` — target platform +- `-ldflags="-s -w"` — strip symbols and DWARF +- `-trimpath` — strip absolute paths + +
+ +### Exercise 3: Version from the Command Line ⭐⭐⭐ + +Write a programme that prints the version number when run with the `--version` flag, and a greeting without it. The version must be embedded at build time via `-ldflags`, not hardcoded. + +```bash +go build -ldflags="-X main.version=2.1.0" -o app . +./app --version +app v2.1.0 +./app +Hello from app! +``` + +
+Hint + +Declare `var version = "dev"` and use `os.Args` to check the arguments. + +
+ +
+Solution + +```go +package main + +import ( + "fmt" + "os" +) + +var version = "dev" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Println("app v" + version) + return + } + fmt.Println("Hello from app!") +} +``` + +Build: + +```bash +go build -ldflags="-X main.version=2.1.0" -o app . +``` + +
+ +### Exercise 4: Multi-Platform Build ⭐⭐⭐ + +Write a script (or commands) that builds the same project for three platforms: Linux amd64, macOS arm64, Windows amd64. All binaries should be static, minimal size, and placed in a `dist/` folder. + +
+Solution + +```bash +mkdir -p dist + +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app-linux . +CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o dist/app-mac . +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app.exe . +``` + +Or via a Makefile: + +```makefile +.PHONY: dist +dist: + mkdir -p dist + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app-linux . + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o dist/app-mac . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app.exe . +``` + +
+ +--- + +## What's Next? + +Now you can turn code into a binary, build for any platform, and understand what goes on behind the scenes of compilation. In the next lesson we'll look at the **Go Playground** — a web environment for running Go code right in the browser: quick experiments, sharing snippets with colleagues, and testing ideas without a local installation. + +--- + +## Sources + +- [Go Command Documentation](https://pkg.go.dev/cmd/go) — official documentation for the `go` command +- [Go at Google: Language Design in the Service of Software Engineering](https://go.dev/talks/2012/splash.article) — Rob Pike on Go's design +- [Go 1.24 Release Notes](https://go.dev/doc/go1.24) — `go run` caching, `tool` directive +- [Go 1.25 Release Notes](https://go.dev/doc/go1.25) — DWARF v5, automatic GOMAXPROCS +- [How to Reduce Go Binary Size](https://words.filippo.io/shrink-your-go-binaries-with-this-one-weird-trick/) — Filippo Valsorda on size optimisation +- [Effective Go](https://go.dev/doc/effective_go) — recommendations for idiomatic Go +- [Understanding Go Compiler](https://www.linkedin.com/pulse/understanding-go-compiler-kanishka-naik-sbmwc) — Kanishka Naik on the Go compilation pipeline + + +--- + + diff --git a/apps/web/src/content/docs/en/program-structure.md b/apps/web/src/content/docs/en/program-structure.md index 2c0e81f..14f9baa 100644 --- a/apps/web/src/content/docs/en/program-structure.md +++ b/apps/web/src/content/docs/en/program-structure.md @@ -857,5 +857,8 @@ Now you know what a Go programme is made of. In the next lesson we'll cover **co ← Previous Hello World -
+ + Next → + Compile and Run + diff --git a/apps/web/src/data/sidebar-unified.json b/apps/web/src/data/sidebar-unified.json index 18fb9fb..7fa2f36 100644 --- a/apps/web/src/data/sidebar-unified.json +++ b/apps/web/src/data/sidebar-unified.json @@ -50,6 +50,9 @@ }, { "label": "Компиляция и запуск", + "translations": { + "en": "Compile and Run" + }, "slug": "compile-and-run" } ] diff --git a/apps/web/src/data/sidebar.json b/apps/web/src/data/sidebar.json index cf133e6..b6e05e0 100644 --- a/apps/web/src/data/sidebar.json +++ b/apps/web/src/data/sidebar.json @@ -65,6 +65,10 @@ { "label": "Program Structure", "link": "/en/program-structure/" + }, + { + "label": "Compile and Run", + "link": "/en/compile-and-run/" } ] } diff --git a/apps/web/src/data/toc.json b/apps/web/src/data/toc.json index 584ec3e..6edf612 100644 --- a/apps/web/src/data/toc.json +++ b/apps/web/src/data/toc.json @@ -73,6 +73,11 @@ "slug": "program-structure", "title": "Program Structure", "number": "1.5" + }, + { + "slug": "compile-and-run", + "title": "Compile and Run", + "number": "1.6" } ] } diff --git a/books/go-language-en/content/01-intro/06-compile-and-run.md b/books/go-language-en/content/01-intro/06-compile-and-run.md index 72fbc72..125f811 100644 --- a/books/go-language-en/content/01-intro/06-compile-and-run.md +++ b/books/go-language-en/content/01-intro/06-compile-and-run.md @@ -2,19 +2,824 @@ title: "Compile and Run" description: "go build, go run and executables" slug: compile-and-run -published: false +published: true author: godojo -updatedAt: "2025-12-15" +updatedAt: "2026-02-20" +readingTime: 14 --- -# Compile and Run +# Compile and Run: What Actually Happens When You Run a Go Programme -> TODO: go build, go run and executables +In the previous lesson we dissected what makes up a Go programme. By now you've typed `go run main.go` a dozen times and seen the output. But what's behind that command? Where does the compiled file go? And how do you turn code into a **binary** — an executable file containing machine instructions that you can simply copy onto a server and run — no Go, no dependencies, no nothing? -## Introduction +In the first lesson we mentioned "compiled language" and "single binary with no dependencies" — time to see what those words mean in practice. Compilation is the translation of your code into the language of the processor: ones and zeroes that the CPU executes directly. The result of that translation is the binary (from the word binary — base-two). Unlike Python, where an interpreter reads and executes your programme's text line by line every time, Go does the translation once — and from that point the processor works with the finished result. That's where the speed comes from. -TODO +Today we pop the bonnet. + +--- + +For all examples in this lesson we'll use our trusty `main.go`: + +```go +package main + +import "fmt" + +func main() { + fmt.Println("Hello, World!") +} +``` + +Open the terminal in VS Code (`` Ctrl+` ``) and make sure you're in the folder with this file. + +--- + +## `go run` — The Illusion of an Interpreter + +If you've come from Python or JavaScript, `go run` feels familiar: type a command — programme runs. No intermediate files, no faffing about. Feels like Go just executes the text. + +But Go is a compiled language. Always. Even when `go run` pretends to be an interpreter, a full-blown build is happening under the bonnet: + +1. A temporary directory is created (the path depends on your OS) +2. The source code is compiled into a native binary +3. The binary is launched as a separate process +4. Once the programme finishes, the temporary directory is deleted + +That's why nothing appears in your working folder — everything lives and dies in a temporary directory. + +### Peeking Under the Bonnet + +Want to see what's going on? Add the `-x` flag: + +```bash +go run -x main.go +``` + +Dozens of lines will scroll through the terminal — every command Go executes behind the scenes. You'll see the path to the temporary directory (`WORK=...`), compiler invocations, and linking. + +If you want to keep the temporary files for inspection, there's the `-work` flag: + +```bash +go run -work main.go +WORK=/var/folders/.../go-build3712456890 # path depends on your OS +Hello, World! +``` + +The directory won't be deleted after the programme finishes. You can go in there and find the actual compiled binary. + +:::tip Handy trick +`go run -work` is a great way to prove to yourself that Go really does produce a proper executable, rather than "interpreting" your code. +::: + +### Passing Arguments + +In the previous lesson we wrote a greeter programme with `os.Args`. Running it looked like this: + +```bash +go run main.go Alice +Hello, Alice! +``` + +Everything after the filename is arguments to your programme. Go sorts out where its own flags end and your data begins. Go flags go **before** the file, programme arguments go **after**: + +```bash +go run -race main.go --port 8080 +# ^^^^ ^^^^^^^^^^ +# Go flag your programme's arguments +``` + +By the way, instead of `go run main.go` you can write **`go run .`** — the dot means "the entire package in the current directory". Whilst you've only got one file there's no difference, but it's the more idiomatic* approach, and later you'll see why. + +:::caution The dot only works with a Go module +Commands using `.` (`go run .`, `go build .`) require a `go.mod` file in your project directory. If there isn't one, Go will throw an error. Create a module with a single command: + +```bash +go mod init hello +``` + +`hello` is the name of your module (can be anything). After this, a `go.mod` file will appear in the directory. We'll talk about modules properly in a separate lesson; for now, just know: **run `go mod init` before using `.` for the first time**. +::: + +* **Idiomatic** means "the way the community does it". Every language has unwritten rules: not just "it works", but "this is how experienced developers write it". In the Go world, `go run .` is idiomatic; `go run main.go` isn't. You'll come across this word often in Go documentation and articles. + +### Limitations + +Since **Go 1.24**, `go run` has caching: if the code hasn't changed, a repeat run grabs the ready-made binary from cache. But there are still plenty of limitations: + +- **Slower than running the binary directly.** Even with caching, `go run` checks whether the build is up to date on every invocation. For large projects the overhead can be significant — in some cases up to 8 times slower than running the compiled binary. +- **No control over the binary.** It sits somewhere in the cache; you can't hand it to someone or ship it to a server. `os.Executable()` returns a path into the temporary directory — if your programme relies on its own path, things will break. +- **Cache is short-lived.** Go aggressively clears cached binaries — roughly after 2 days of non-use. Come morning, `go run` might recompile from scratch. +- **`package main` only.** You can't run a library package — only executable programmes. +- **Cross-compilation is pointless.** `GOOS=linux go run .` doesn't make sense — the binary runs on your machine, not on the target platform. +- **Not for production.** Production is the environment where your programme runs "for real": serving actual users, running on a server round the clock. The opposite is the development environment, where you write and test code on your own machine. `go run` is a development tool, not a deployment method. + +**Bottom line:** `go run` is for a quick look. For everything else, there's `go build`. + +--- + +## `go build` — Creating a Proper Binary + +When you need a file you can send to a colleague, upload to a server, or stick in a Docker container (Docker is a system for packaging and running applications in isolated environments — we'll get to know it later) — reach for `go build`. + +```bash +go build -o myapp . +./myapp +Hello, World! +``` + +A file called `myapp` has appeared in the directory (on Windows it'd be `myapp.exe`), about 2 MB or a touch more. It's a self-contained binary. The target machine doesn't need Go, doesn't need libraries, doesn't need a runtime. Just copy the file and run it. + +### Output File Name + +Without the `-o` flag, Go picks the name itself: + +```bash +go build . # Name from go.mod (or the directory name) +go build -o server . # Explicitly set a name +go build -o bin/app . # You can specify a path too +``` + +On Windows, `.exe` is added automatically. + +:::tip `go build` without `package main` +If you run `go build .` in a directory with a library package (not `main`), Go will compile the code and check it for errors but **won't create an output file**. A handy way to validate code without cluttering the directory — especially in CI. +::: + +### The `-race` Flag — Data Race Detector + +Imagine two people editing the same document at the same time, unable to see each other. One writes a heading, the other deletes it — the result is unpredictable. In programming this is called a **data race** — when several parts of a programme simultaneously read and modify the same variable. The outcome depends on who got there first, and each run can produce a different result. + +Go can find these situations automatically. Here's an example — don't try to parse every line just yet; we'll cover the `go` keyword in the concurrency lesson. What matters now is the principle: + +```go +package main + +import "fmt" + +func main() { + count := 0 + for i := 0; i < 1000; i++ { + go func() { // launch 1000 parallel tasks + count++ // all writing to the same variable + }() + } + fmt.Println(count) +} +``` + +Run without the flag — the programme silently produces an unpredictable result: + +```bash +go run . +0 # or 127, or 999 — different every time +``` + +Now with `-race`: + +```bash +go run -race main.go +================== +WARNING: DATA RACE +Read at 0x00c00011c028 by goroutine 9: + main.main.func1() + /home/user/main.go:9 +0x2e + +Previous write at 0x00c00011c028 by goroutine 8: + main.main.func1() + /home/user/main.go:9 +0x44 + +Goroutine 9 (running) created at: + main.main() + /home/user/main.go:8 +0x4a + +Goroutine 8 (finished) created at: + main.main() + /home/user/main.go:8 +0x4a +================== +... 2 more similar warnings ... +651 +Found 3 data race(s) +exit status 66 +``` + +Go found **3 races** and shows precisely: line 9, variable `count` (address `0x00c00011c028`), several parallel tasks writing to it simultaneously. The programme exited with code 66 — a special error code for races. The number `651` instead of the expected `1000` is the result of lost updates, a classic consequence of a data race. Without `-race` the programme would silently give wrong results — a bug that's extremely hard to catch by hand. + +The `-race` flag works with `go build`, `go run`, and `go test`. A binary built with it is slower and uses more memory, so you don't ship it to production. But in tests and during development — it's standard practice. + +### Why Does Hello World Weigh 2 Megabytes? + +After their first `go build`, beginners are surprised: a five-line programme — 2 MB? In C the equivalent is 16 KB. + +The thing is, a Go binary isn't just your code. It's an entire universe: + +- **Go runtime** — the execution environment +- **Garbage Collector** — automatic memory management +- **Goroutine Scheduler** — the goroutine scheduler (remember from the first lesson — multitasking out of the box?) +- **Symbol table and debug information** — for panic traces and debugging +- Parts of the standard library that you imported + +A single `import "fmt"` pulls in reflection*, I/O*, and string formatting*. All baked in. + +- * **Reflection** — a mechanism that allows a programme to analyse and modify its own structure at runtime: discover variable types, read their values, call functions by name. In formal terms, it's the ability of a programme to examine its own type system whilst running. In plainer terms — the ability of a programme to "look at itself". Think about it: you write `fmt.Println(42)` and get `42`. You write `fmt.Println("hello")` and get `hello`. You write `fmt.Println(3.14)` and get `3.14`. How does `Println` know how to print each of these values when a number and a string are completely different things? Through reflection: at the moment of the call, the function asks the runtime "what was I given — a number? a string? something else?" — and based on the answer, chooses how to display it. Without reflection you'd have to write a separate function for every data type. +- * **I/O** (Input/Output) — everything to do with reading and writing: output to the terminal, reading files, sending data over the network. `fmt.Println` writes text to standard output (stdout) — that's I/O. +- * **String formatting** — turning data into readable text. When you write `fmt.Println("Answer:", 42)`, Go converts the number `42` into the string `"42"` and glues it to `"Answer:"`. + +This is why in the first lesson we said "single binary with no dependencies" — now you see what that means. Go puts everything it needs right into the file. + +### Reducing the Size + +Why bother making the binary smaller? A smaller binary means faster downloads to servers (especially if you've got dozens of them), less space in the Docker image, faster container starts. For Hello World the difference is negligible, but when a project grows to 20–50 MB, shaving off 25–30% starts to matter. + +```bash +# Standard build +go build -o app . +# app — ~2 MB + +# Without debug information (-s: symbols, -w: DWARF) +go build -ldflags="-s -w" -o app . +# app — ~1.5 MB +``` + +The `-s -w` flags strip the symbol table and DWARF debug information. In practice that's **minus 25–30%** of the size. Panic traces still work — Go keeps its own internal table for error tracking separately. + +:::tip Production recipe +```bash +go build -ldflags="-s -w" -trimpath -o app . +``` +The `-trimpath` flag additionally strips absolute filesystem paths from the binary. A bonus for security and build reproducibility. +::: + +### Embedding the Version + +Picture this: you've shipped a binary to a server. A month later something breaks. Which version of the code is running there — you can't remember. You've rebuilt ten times since then. If the binary can answer "I'm version 1.0.0" on its own — the problem is solved in a second: run it with `--version`, compare with the current release, work out whether you need to update. + +That's exactly why the version is baked into the binary at build time: + +```go +package main + +import "fmt" + +var version = "dev" + +func main() { + fmt.Println("Version:", version) +} +``` + +In the code, `version` is `"dev"` — the default value for development. But at build time we swap it out: + +```bash +go build -ldflags="-X main.version=1.0.0" -o app . +./app +Version: 1.0.0 +``` + +The `-X` flag replaces the value of a string variable at compile time — no need to change the source code. In CI/CD systems (Continuous Integration / Continuous Delivery — automated building and shipping of code) this is standard practice: they embed the version number, build date, and Git commit hash. Any binary can "introduce itself". + +:::tip From Go 1.24 onwards +Go automatically embeds Git information (commit hash, version tag, a `+dirty` marker if there are unsaved changes). You can view it with `go version -m ./app`. +::: + +### Build Cache — Why the Second Time Is Faster + +A **cache** is a place where results of previous work are stored so you don't have to redo it. Your browser caches images from websites so it doesn't download them again. Go does the same with compilation: it saves already-compiled packages, and on the next build only recompiles those that changed. + +```bash +go build -o app . # First time: ~2 seconds +go build -o app . # Second time: ~0.3 seconds (linking only) +``` + +To find out where exactly Go keeps the cache on your machine: + +```bash +go env GOCACHE +``` + +The path depends on your OS — it's different on Linux, macOS, and Windows. If something's gone wrong and you want to clear the cache — `go clean -cache`. + +:::tip For the experienced: PGO — Profile-Guided Optimisation +Since Go 1.22, Profile-Guided Optimisation has been stable. The idea: you capture a CPU profile from a running application (via `pprof`), drop the `default.pgo` file in the project root, and on the next build the compiler uses real data about hot paths for aggressive inlining and optimisation. Performance gain: **2–14%** without changing a single line of code. More in the lessons on testing and profiling. +::: + +--- + +## `go install` — Installing Tools Globally + +`go build` creates a binary in the current folder — wherever you happen to be in the terminal. That's handy for your project, but what if you want to install **someone else's tool** — a linter, formatter, code generator — and use it from anywhere on the system? + +That's what `go install` is for. It does the same as `go build`, but places the finished binary not next to you, but in a special folder. In the installation lesson we saw the [`GOBIN`](/en/installation/#setting-up-environment-variables) variable in a table — this is it. To find out where exactly Go puts tools on your system: + +```bash +go env GOPATH +``` + +Binaries end up in the `bin` subfolder of that path. If you added that folder to `PATH` when installing Go, the tools will be available from any directory. If not — now's the time to do it (details in the installation lesson). + +Example — let's install `goimports`, a tool for automatically sorting imports: + +```bash +go install golang.org/x/tools/cmd/goimports@latest +``` + +This command downloads the `goimports` source code from `golang.org/x/tools`, compiles it, and puts the finished binary in that `bin` folder. The `@latest` suffix means "latest version". The tool is now available globally: + +```bash +goimports -w main.go +``` + +This works from any folder — provided the `bin` folder is in your `PATH`. This is exactly how the tools in the editor lesson were installed — `gopls`, `dlv`, and other VS Code extensions were installed via `go install`. + +### Difference from `go build` + +| | `go build` | `go install` | +|---|---|---| +| **Where the binary goes** | Current folder | The `bin` folder inside `GOPATH` | +| **What for** | Your project | Third-party tools | +| **Available globally** | No | Yes (if `bin` is in `PATH`) | +| **Typical usage** | `go build -o server .` | `go install tool@latest` | + +:::tip For the experienced: the `tool` directive in go.mod (Go 1.24) +Previously, to pin a tool's version (linter, code generator) across a project, developers used a hack — a `tools.go` file with blank imports. Since Go 1.24, there's a `tool` directive right in `go.mod`: + +```bash +go get -tool github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +This adds a `tool` line to `go.mod`, and now any team member runs the tool via `go tool golangci-lint run ./...` — guaranteed to be the same version as everyone else. No more discrepancies between developers and CI. +::: + +### When to Use What + +| | `go run` | `go build` | `go install` | +|---|----------|------------|--------------| +| **What it does** | Compiles → runs → deletes | Compiles → saves | Compiles → puts in `GOPATH/bin` | +| **File remains** | ❌ No | ✅ In current folder | ✅ In `GOPATH/bin` | +| **When to use** | Development, experiments | Building for server, Docker | Installing CLI tools | +| **Caching** | Since Go 1.24 | Intermediate files | Intermediate files | + +**Typical workflow:** +1. `go run .` — whilst you're writing and debugging +2. `go build` — when you need the final artefact +3. `go install` — for tools you use regularly + +--- + +## Cross-Compilation — Building for Any OS + +An important point: every binary is built **for a specific OS and architecture**. A binary built on macOS won't run on Linux — you'll get an `Exec format error`. And vice versa. This isn't like a Python script that runs the same everywhere there's an interpreter. A binary contains machine instructions for a specific platform. + +If you develop on macOS but your server runs Linux, you need to build the binary specifically for Linux. This is **cross-compilation**: building on one platform for another. In the first lesson we mentioned "built for Linux on a Mac with a single command" — time to show how. + +Remember the code from the installation lesson? + +```go +fmt.Printf("OS: %s\n", runtime.GOOS) +fmt.Printf("Arch: %s\n", runtime.GOARCH) +``` + +Back then, `runtime.GOOS` showed your current OS — and that makes sense, since you were building and running on the same machine. But `runtime.GOOS` and `runtime.GOARCH` aren't "detecting the system at run time". They're constants baked into the binary at compile time. By default Go sets them to your OS and architecture, so everything matches. But you can override them with two environment variables — and then the binary will be built for a **different** platform: + +```bash +# Build on Mac, run on a Linux server +GOOS=linux GOARCH=amd64 go build -o app-linux . + +# For Windows +GOOS=windows GOARCH=amd64 go build -o app.exe . + +# For Mac with Apple Silicon +GOOS=darwin GOARCH=arm64 go build -o app-mac . + +# For Raspberry Pi +GOOS=linux GOARCH=arm GOARM=7 go build -o app-rpi . +``` + +No extra compilers, toolchains, or virtual machines. Go does it all out of the box. + +Want to see every supported platform? There are over 45: + +```bash +go tool dist list +aix/ppc64 +android/amd64 +darwin/amd64 +darwin/arm64 +js/wasm +linux/amd64 +linux/arm64 +wasip1/wasm +windows/amd64 +... and 40+ more +``` + +### `CGO_ENABLED=0` — A Truly Static Binary + +We said the Go binary is self-contained — everything it needs is inside. But that's not quite the full picture. By default, Go sometimes uses **C libraries** from the operating system — for instance, for DNS resolution (translating domain names into IP addresses) and for looking up system user information. This is called **CGO** (C-Go) — a bridge between Go and C code. + +The problem is that C libraries differ across systems. A binary built with CGO expects a particular C library on the target system. If it's not there or it's a different version, the binary crashes with a cryptic error. + +To make the binary **truly** self-contained, with no dependencies on anything on the target system, disable CGO: + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app . +``` + +`CGO_ENABLED=0` tells Go: "don't use C libraries, do everything yourself". Go will replace system calls with its own pure-Go implementations. The result is a binary that needs nothing but the operating system. No libraries, no dependencies. + +:::danger This matters for Docker +The popular Alpine Linux base image uses `musl libc` instead of the standard `glibc`. If you build a binary without `CGO_ENABLED=0`, it'll crash in Alpine with the baffling error `no such file or directory` — even though the file is right there. The system can't find the C library the binary expects. Simple rule: **for Docker builds, always set `CGO_ENABLED=0`**. +::: + +--- + +## Under the Bonnet: Why Go Compiles in Seconds + +In the first lesson we said that Go was created because its creators were fed up waiting 45 minutes for C++ to compile. Here's how they solved the problem. + +### Compilation Stages (On the Back of a Napkin) + +When you type `go build`, here's what happens: + +![Go compilation pipeline](/images/go-compilation-pipeline.png) + +1. **Lexer (Scanner)** — splits the text into tokens: keywords, names, numbers, operators +2. **Parser** — assembles tokens into a tree (AST — Abstract Syntax Tree) reflecting the programme's structure +3. **Semantic Analysis (Type Checker)** — verifies types: "x is an int, you can't add it to a string" +4. **Intermediate Representation** — converts the AST into an intermediate representation (IR) +5. **SSA (Static Single Assignment)** — optimises the IR: removes dead code, folds constants +6. **Machine Code Generation** — turns SSA into instructions for the target processor +7. **Linker** — stitches machine code together with the runtime → finished binary +8. **Execution** — the OS loads the binary and runs it + +You don't have to understand every step. The key thing to know is that compilation is a **translation**: human text → machine instructions. Go does this translation very fast. + +:::tip For the experienced: what happens at each stage +**SSA (Static Single Assignment)** is the key intermediate representation. Each variable is assigned a value exactly once: `x = 1; x = x + 2` becomes `x₁ = 1; x₂ = x₁ + 2`. This simplifies optimisations: dead code elimination, constant folding, removing unnecessary array bounds checks. + +**Escape analysis** — the compiler decides where a variable lives: on the stack (fast, free cleanup) or on the heap (costlier, loads the GC — Garbage Collector, the system that automatically frees unused memory). If a reference to a variable "escapes" beyond the function — heap. Otherwise — stack. To see the compiler's decisions: `go build -gcflags="-m" .` + +**Inlining** — the compiler substitutes the body of small functions directly at the call site, eliminating call overhead. Go inlines functions up to a certain "cost" (80 AST nodes). Since Go 1.22, the inliner has become more aggressive. +::: + +:::tip For the experienced: what happens before `main()` +When the OS launches a Go binary, your `main()` is still a long way off: + +1. **OS Loader** loads the file into memory, finds the entry point +2. **Assembly bootstrap** (`_rt0_amd64_linux` and friends) saves argc/argv +3. **`runtime.rt0_go`** allocates a stack for the system goroutine g0, initialises the heap +4. **Subsystem startup** — GC, goroutine scheduler, network poller +5. **`init()` functions** of all imported packages — bottom-up through the dependency tree +6. **`main.main()`** — your code, at last + +This is why even an empty Go programme is "heavier" than its C equivalent — it carries a full runtime environment with it. +::: + +### Why Faster Than C++ and Rust + +**The main reason is the dependency model.** Rob Pike measured it: when compiling Go code, the compiler reads **40 times** less source text than when compiling C++. In C++, every `#include ` re-explains to the compiler what strings are. In Go, package information is stored in compiled form — the compiler reads only direct imports without diving into their dependencies. + +**Other factors:** +- **25 keywords** — the parser works in a flash +- **No circular imports** — the dependency graph can be compiled in parallel +- **Unused import = error** — the compiler doesn't waste time on dead code +- **No C++-style templates** — no code bloat during instantiation + +For scale: the Istio project (a platform for managing microservices, ~350,000 lines of Go) compiles from scratch in **33 seconds** on a beefy machine. With a warm cache — under a second. + +:::tip For the curious +`go build -x` shows every command the toolchain executes: compiling each package, generating configs, linking. Dozens of output lines — and all of it in a couple of seconds. +::: + +--- + +## Practical Scenarios + +### Docker Multi-Stage Build + +The standard way to ship a Go service to a server — a multi-stage Docker build: + +```dockerfile +# Stage 1: build the binary +FROM golang:1.25 AS builder +WORKDIR /app +COPY go.mod go.sum ./ # go.sum — a file with dependency checksums +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -trimpath -o server . + +# Stage 2: minimal image +FROM scratch +COPY --from=builder /app/server /server +ENTRYPOINT ["/server"] +``` + +The `scratch` image is completely empty. No bash, no curl, no libc. Just your binary. Final image: **3–5 MB** instead of 700 MB+ with the full SDK (Software Development Kit — a set of development tools). This is possible precisely because the Go binary is self-contained. + +### Makefile + +A Makefile is a file containing a set of command recipes. Instead of typing out a long build command with a dozen flags every time, you describe it once in a Makefile and then call it with a short `make build`. The `make` utility comes pre-installed on Linux and macOS; for Windows it can be installed separately. + +For larger projects, wrapping commands in a Makefile is handy: + +```makefile +APP_NAME = myapp +.PHONY: run build test clean cross + +run: + go run . +build: + go build -ldflags="-s -w" -trimpath -o $(APP_NAME) . +test: + go test -race ./... +clean: + rm -f $(APP_NAME) $(APP_NAME)-* +cross: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o $(APP_NAME)-linux . + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -o $(APP_NAME)-mac . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o $(APP_NAME).exe . +``` + +Now it's `make build` instead of a long command with flags. `make cross` — build for three OSes in one go. + +:::tip For the experienced: GoReleaser +When your project grows to the point of public releases — cross-compilation, archives, changelogs, publishing to GitHub/GitLab, Homebrew and Scoop configs — doing it by hand is a nightmare. **GoReleaser** automates the entire cycle. One `.goreleaser.yaml` file, one command — and a release for dozens of platforms is ready. It's used by Kubernetes, Docker, GitHub CLI, and thousands of open-source projects. +::: + +--- + +## Common Beginner Blunders + +### 1. "undefined" when running `go run main.go` + +As a project grows, code naturally splits into multiple files — say, `main.go` and `math.go` in the same `main` package. And here's the catch. + +When you explicitly list files — `go run main.go` — Go considers only the listed files part of your package. Other `.go` files in the same directory are invisible, as if they don't exist: + +```bash +go run main.go +./main.go:10:2: undefined: Add # the Add function from math.go — "doesn't exist" + +go run . # ✅ all package files included +``` + +It's important not to confuse two things: +- **Neighbouring files of the same package** (e.g. `math.go` with `package main` in the same folder) — with `go run main.go`, they **aren't picked up**. This is the problem. +- **Imported packages** (via `import`) — are picked up just fine. If you have `import "myproject/utils"`, the `utils` package will compile because it's resolved through the module system, not by scanning neighbouring files. + +`go run .` and `go build .` include **all `.go` files** in the directory (except `_test.go`). So get into the habit of `go run .` — you won't run into this problem. + +### 2. "cannot run non-main package" + +```bash +go run . +go run: cannot run non-main package +``` + +You forgot to write `package main` or there's no `main()` function. Remember from the previous lesson: only `package main` + `func main()` creates an executable programme. + +### 3. Binary doesn't run on another OS + +Built on Mac, copied to a Linux server: + +```bash +./myapp +bash: ./myapp: cannot execute binary file: Exec format error +``` + +That's a macOS binary, and you're trying to run it on Linux. You need cross-compilation: + +```bash +GOOS=linux GOARCH=amd64 go build -o myapp . +``` + +### 4. "permission denied" (Linux/macOS) + +```bash +./myapp +bash: ./myapp: Permission denied +``` + +No execute permission. `go build` sets it automatically, but when copying via an archive or over the network, the execute bit can get lost. On Windows this isn't a problem — it works differently there. + +```bash +chmod +x myapp +``` + +### 5. Mysterious "no such file or directory" in Docker + +```bash +exec /server: no such file or directory +``` + +The file's right there, but the system can't find the dynamic loader `glibc`. You built without `CGO_ENABLED=0` and you're running in Alpine with `musl`. + +```bash +# Correct build for Docker: +CGO_ENABLED=0 go build -o server . +``` + +--- + +## Full Example: From Code to Binary + +Let's take the greeter programme from the previous lesson and walk through the complete cycle: + +```go +// main.go +package main + +import ( + "fmt" + "os" + "strings" +) + +var version = "dev" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Println(version) + return + } + + name := "World" + if len(os.Args) > 1 { + name = strings.Join(os.Args[1:], " ") + } + fmt.Printf("Hello, %s!\n", name) +} +``` + +```bash +# 1. Quick run during development +go run . Alice +Hello, Alice! + +# 2. Build a binary with the version baked in +go build -ldflags="-s -w -X main.version=1.0.0" -trimpath -o greeter . + +# 3. Verify +./greeter --version +1.0.0 +./greeter dear friend +Hello, dear friend! + +# 4. Cross-compile for a Linux server +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -ldflags="-s -w -X main.version=1.0.0" -trimpath -o greeter-linux . + +# 5. Both files — about 1.3 MB each +``` + +One source file — binaries for any platform. No Docker, no virtual machines, no pain. + +--- ## Summary -TODO +| Command / Concept | What to Remember | +|---------------------|-------------| +| `go run .` | Compiles and runs. Binary is temporary. For development. | +| `go build -o app .` | Creates a permanent binary. For deployment and CI/CD. | +| `go install` | Puts a binary in `GOPATH/bin`. For CLI tools. | +| `-ldflags="-s -w"` | Strips debug info. Minus 25–30% size. | +| `-trimpath` | Strips paths. Security + reproducibility. | +| `-X main.var=val` | Embeds a variable value at build time. | +| `GOOS` / `GOARCH` | Cross-compilation. Any OS, any architecture. | +| `CGO_ENABLED=0` | Fully static binary. Required for Docker. | +| `go build -x` | Shows all compilation steps. For the curious. | + +--- + +## Exercises + +### Exercise 1: Peeking Under the Bonnet ⭐ + +Run the Hello World programme so that you see the path to the temporary build directory. The temporary files should not be deleted after the run. + +
+Solution + +```bash +go run -work main.go +WORK=/var/folders/.../go-build1234567890 # path depends on your OS +Hello, World! +``` + +The `-work` flag prints the path and preserves the temporary directory. You can go in and find the compiled binary. + +
+ +### Exercise 2: Optimal Build ⭐⭐ + +Build a binary for Linux ARM64 with minimal size, no filesystem paths, and no C library dependencies. Name the file `server`. + +
+Solution + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ + go build -ldflags="-s -w" -trimpath -o server . +``` + +- `CGO_ENABLED=0` — static binary with no C dependencies +- `GOOS=linux GOARCH=arm64` — target platform +- `-ldflags="-s -w"` — strip symbols and DWARF +- `-trimpath` — strip absolute paths + +
+ +### Exercise 3: Version from the Command Line ⭐⭐⭐ + +Write a programme that prints the version number when run with the `--version` flag, and a greeting without it. The version must be embedded at build time via `-ldflags`, not hardcoded. + +```bash +go build -ldflags="-X main.version=2.1.0" -o app . +./app --version +app v2.1.0 +./app +Hello from app! +``` + +
+Hint + +Declare `var version = "dev"` and use `os.Args` to check the arguments. + +
+ +
+Solution + +```go +package main + +import ( + "fmt" + "os" +) + +var version = "dev" + +func main() { + if len(os.Args) > 1 && os.Args[1] == "--version" { + fmt.Println("app v" + version) + return + } + fmt.Println("Hello from app!") +} +``` + +Build: + +```bash +go build -ldflags="-X main.version=2.1.0" -o app . +``` + +
+ +### Exercise 4: Multi-Platform Build ⭐⭐⭐ + +Write a script (or commands) that builds the same project for three platforms: Linux amd64, macOS arm64, Windows amd64. All binaries should be static, minimal size, and placed in a `dist/` folder. + +
+Solution + +```bash +mkdir -p dist + +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app-linux . +CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o dist/app-mac . +CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app.exe . +``` + +Or via a Makefile: + +```makefile +.PHONY: dist +dist: + mkdir -p dist + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app-linux . + CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -trimpath -o dist/app-mac . + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -trimpath -o dist/app.exe . +``` + +
+ +--- + +## What's Next? + +Now you can turn code into a binary, build for any platform, and understand what goes on behind the scenes of compilation. In the next lesson we'll look at the **Go Playground** — a web environment for running Go code right in the browser: quick experiments, sharing snippets with colleagues, and testing ideas without a local installation. + +--- + +## Sources + +- [Go Command Documentation](https://pkg.go.dev/cmd/go) — official documentation for the `go` command +- [Go at Google: Language Design in the Service of Software Engineering](https://go.dev/talks/2012/splash.article) — Rob Pike on Go's design +- [Go 1.24 Release Notes](https://go.dev/doc/go1.24) — `go run` caching, `tool` directive +- [Go 1.25 Release Notes](https://go.dev/doc/go1.25) — DWARF v5, automatic GOMAXPROCS +- [How to Reduce Go Binary Size](https://words.filippo.io/shrink-your-go-binaries-with-this-one-weird-trick/) — Filippo Valsorda on size optimisation +- [Effective Go](https://go.dev/doc/effective_go) — recommendations for idiomatic Go +- [Understanding Go Compiler](https://www.linkedin.com/pulse/understanding-go-compiler-kanishka-naik-sbmwc) — Kanishka Naik on the Go compilation pipeline From b121455c4f7843459700b113a80ce9682fa4132c Mon Sep 17 00:00:00 2001 From: Alex B Date: Fri, 20 Feb 2026 01:04:49 +0200 Subject: [PATCH 3/4] feat: add Vercel Web Analytics --- apps/web/astro.config.mjs | 5 +++++ apps/web/package.json | 1 + 2 files changed, 6 insertions(+) diff --git a/apps/web/astro.config.mjs b/apps/web/astro.config.mjs index 9148073..672df2a 100644 --- a/apps/web/astro.config.mjs +++ b/apps/web/astro.config.mjs @@ -61,6 +61,11 @@ export default defineConfig({ maxHeadingLevel: 3, }, head: [ + // Vercel Analytics + { + tag: 'script', + attrs: { defer: true, src: '/_vercel/insights/script.js' }, + }, // Open Graph { tag: 'meta', diff --git a/apps/web/package.json b/apps/web/package.json index 374b7ff..e2f94fa 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -13,6 +13,7 @@ "dependencies": { "@astrojs/starlight": "^0.30.0", "@astrojs/vercel": "^9.0.2", + "@vercel/analytics": "^1.6.1", "astro": "^5.0.0", "sharp": "^0.33.0" }, From 078445fe5fbe8f753930713938c0c7cd7ab9de02 Mon Sep 17 00:00:00 2001 From: Alex B Date: Fri, 20 Feb 2026 01:11:56 +0200 Subject: [PATCH 4/4] fix: correct Python compilation description --- apps/web/src/content/docs/en/compile-and-run.md | 2 +- apps/web/src/content/docs/ru/compile-and-run.md | 2 +- books/go-language-en/content/01-intro/06-compile-and-run.md | 2 +- books/go-language-ru/content/01-intro/06-compile-and-run.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/content/docs/en/compile-and-run.md b/apps/web/src/content/docs/en/compile-and-run.md index 7ead96e..1a7c9ba 100644 --- a/apps/web/src/content/docs/en/compile-and-run.md +++ b/apps/web/src/content/docs/en/compile-and-run.md @@ -11,7 +11,7 @@ readingTime: 29 --- In the previous lesson we dissected what makes up a Go programme. By now you've typed `go run main.go` a dozen times and seen the output. But what's behind that command? Where does the compiled file go? And how do you turn code into a **binary** — an executable file containing machine instructions that you can simply copy onto a server and run — no Go, no dependencies, no nothing? -In the first lesson we mentioned "compiled language" and "single binary with no dependencies" — time to see what those words mean in practice. Compilation is the translation of your code into the language of the processor: ones and zeroes that the CPU executes directly. The result of that translation is the binary (from the word binary — base-two). Unlike Python, where an interpreter reads and executes your programme's text line by line every time, Go does the translation once — and from that point the processor works with the finished result. That's where the speed comes from. +In the first lesson we mentioned "compiled language" and "single binary with no dependencies" — time to see what those words mean in practice. Compilation is the translation of your code into the language of the processor: ones and zeroes that the CPU executes directly. The result of that translation is the binary (from the word binary — base-two). Unlike Python, where code is first compiled to bytecode and then executed by a virtual machine (CPython VM), Go compiles straight to native machine instructions — no intermediate layers. That's where the speed comes from. Today we pop the bonnet. diff --git a/apps/web/src/content/docs/ru/compile-and-run.md b/apps/web/src/content/docs/ru/compile-and-run.md index ff639f2..ddc2536 100644 --- a/apps/web/src/content/docs/ru/compile-and-run.md +++ b/apps/web/src/content/docs/ru/compile-and-run.md @@ -11,7 +11,7 @@ readingTime: 26 --- В предыдущем уроке мы разобрали, из каких деталей состоит Go-программа. Вы уже десяток раз набирали `go run main.go` и видели результат. Но что стоит за этой командой? Куда девается скомпилированный файл? И как превратить код в **бинарник** — исполняемый файл с машинными инструкциями, который можно просто скопировать на сервер и запустить — без Go, без зависимостей, без ничего? -В первом уроке мы упоминали "компилируемый язык" и "один бинарник без зависимостей" — пришло время разобраться, что за этими словами стоит на практике. Компиляция — это перевод вашего кода в язык процессора: нули и единицы, которые CPU выполняет напрямую. Результат этого перевода — тот самый бинарник (от слова binary — двоичный). В отличие от Python, где интерпретатор каждый раз читает и выполняет текст программы строка за строкой, Go делает перевод один раз — а дальше процессор работает с готовым результатом. Отсюда и скорость. +В первом уроке мы упоминали "компилируемый язык" и "один бинарник без зависимостей" — пришло время разобраться, что за этими словами стоит на практике. Компиляция — это перевод вашего кода в язык процессора: нули и единицы, которые CPU выполняет напрямую. Результат этого перевода — тот самый бинарник (от слова binary — двоичный). В отличие от Python, где код сначала компилируется в байткод, а затем выполняется виртуальной машиной (CPython VM), Go компилирует сразу в машинные инструкции процессора — без промежуточных слоёв. Отсюда и скорость. Сегодня снимаем капот. diff --git a/books/go-language-en/content/01-intro/06-compile-and-run.md b/books/go-language-en/content/01-intro/06-compile-and-run.md index 125f811..7a28faf 100644 --- a/books/go-language-en/content/01-intro/06-compile-and-run.md +++ b/books/go-language-en/content/01-intro/06-compile-and-run.md @@ -12,7 +12,7 @@ readingTime: 14 In the previous lesson we dissected what makes up a Go programme. By now you've typed `go run main.go` a dozen times and seen the output. But what's behind that command? Where does the compiled file go? And how do you turn code into a **binary** — an executable file containing machine instructions that you can simply copy onto a server and run — no Go, no dependencies, no nothing? -In the first lesson we mentioned "compiled language" and "single binary with no dependencies" — time to see what those words mean in practice. Compilation is the translation of your code into the language of the processor: ones and zeroes that the CPU executes directly. The result of that translation is the binary (from the word binary — base-two). Unlike Python, where an interpreter reads and executes your programme's text line by line every time, Go does the translation once — and from that point the processor works with the finished result. That's where the speed comes from. +In the first lesson we mentioned "compiled language" and "single binary with no dependencies" — time to see what those words mean in practice. Compilation is the translation of your code into the language of the processor: ones and zeroes that the CPU executes directly. The result of that translation is the binary (from the word binary — base-two). Unlike Python, where code is first compiled to bytecode and then executed by a virtual machine (CPython VM), Go compiles straight to native machine instructions — no intermediate layers. That's where the speed comes from. Today we pop the bonnet. diff --git a/books/go-language-ru/content/01-intro/06-compile-and-run.md b/books/go-language-ru/content/01-intro/06-compile-and-run.md index a32f9e9..375559a 100644 --- a/books/go-language-ru/content/01-intro/06-compile-and-run.md +++ b/books/go-language-ru/content/01-intro/06-compile-and-run.md @@ -12,7 +12,7 @@ readingTime: 14 В предыдущем уроке мы разобрали, из каких деталей состоит Go-программа. Вы уже десяток раз набирали `go run main.go` и видели результат. Но что стоит за этой командой? Куда девается скомпилированный файл? И как превратить код в **бинарник** — исполняемый файл с машинными инструкциями, который можно просто скопировать на сервер и запустить — без Go, без зависимостей, без ничего? -В первом уроке мы упоминали "компилируемый язык" и "один бинарник без зависимостей" — пришло время разобраться, что за этими словами стоит на практике. Компиляция — это перевод вашего кода в язык процессора: нули и единицы, которые CPU выполняет напрямую. Результат этого перевода — тот самый бинарник (от слова binary — двоичный). В отличие от Python, где интерпретатор каждый раз читает и выполняет текст программы строка за строкой, Go делает перевод один раз — а дальше процессор работает с готовым результатом. Отсюда и скорость. +В первом уроке мы упоминали "компилируемый язык" и "один бинарник без зависимостей" — пришло время разобраться, что за этими словами стоит на практике. Компиляция — это перевод вашего кода в язык процессора: нули и единицы, которые CPU выполняет напрямую. Результат этого перевода — тот самый бинарник (от слова binary — двоичный). В отличие от Python, где код сначала компилируется в байткод, а затем выполняется виртуальной машиной (CPython VM), Go компилирует сразу в машинные инструкции процессора — без промежуточных слоёв. Отсюда и скорость. Сегодня снимаем капот.