Совсем другая книга про Go#

«Маленькие книги» Карла Сегуина — лучшее, что только есть в жанре «быстрого старта». Только самое главное. Без подробностей, в которых так легко утонуть поначалу, но которые потом так же легко восполнить правильно заданными вопросами и самостоятельно найденными ответами.

Когда-то «Маленькая книга про Go» вдохновила меня написать несколько других «маленьких книг» на темы, для которых книг Карла не было. А потом, когда я сам пришел к Go и вернулся к этой книге, вдруг оказалось, что многое изменилось. Это будет совсем другая книга про Go. Не судите строго.

Go это «C++ для инженеров». Пока адепты «поэзии кода» в спорах ищут идеальный баланс между абстракцией и производительностью, инженеры просто делают рабочее решение и уходят домой. В Google нуждались в простом и эффективном инструменте для быстрой разработки и внедрения безопасных, производительных и надежных решений. В итоге получился компактный компилируемый язык со строгой типизацией, автоматическим управлением памятью, встроенной поддержкой многозадачности, модульной организацией кода и собственным менеджером пакетов, дающем доступ к большой библиотеке готовых решений, позволяющих даже новичку быть достаточно эффективным.

Это больше, чем декларация. Это философия, которую идеально иллюстрирует шутка «прибор должен работать в корпусе, а не в принципе». Go именно такой — во многом ограниченный, но предсказуемый. И придерживаться этой философии при написании программ — самый лучший способ быть эффективным. Делать рабочие программы, а не идеальный код. Возможно, это скучнее, чем писать на С++ или Rust. Но хорошего в нем тоже очень много.

И еще. Go хорош во всем, кроме кривой обучения. В качестве первого языка он не то, чтобы сложен, но… здесь с самого начала надо знать то, что обычно изучается в последнюю очередь. В сущности, изучать его приходится в обратном порядке — от сложного к простому. Это настоящий вызов. Но я попробую. Опять же, не судите строго.

Первая программа#

У пресловутого “Hello, World” есть несколько целей. Во-первых, убедиться, что всё нужное для разработки установлено и настроено. Во-вторых, как на первом свидании, увидеть в действии и оценить перспективы. В-третьих, преодолеть психологический барьер: программа уже написана и работает – осталось только понять, как именно.

В любом текстовом редакторе создайте файл hello.go:

package main

func main() {
    println("Hello, world!")
}

Выполните его:

$ go run hello.go
Hello, world!

Или скомпилируйте и выполните:

$ go build hello.go
$ ./hello
Hello, world!

Совсем другая книга, я сказал? Да, другая. И не судите по первой программе, ее простота обманчива. Я сказал «в обратном порядке», так давайте начнем с конца.

Программы#

Программа на Go это двоичный исполнимый файл для процессоров:

  • amd64 — x86-64 (Intel, AMD)
  • arm64 — 64-бит ARM (Apple Silicon, сервера Graviton, Raspberry Pi 4+)
  • arm — 32-бит ARM
  • 386 — 32-бит x86
  • riscv64 — RISC-V 64-бит
  • wasm — WebAssembly (для браузеров)
  • ppc64 / ppc64le — PowerPC
  • mips / mipsle / mips64 / mips64le — MIPS
  • s390x — IBM System/390
  • loong64 — LoongArch (китайские Loongson)

в средах:

  • android — ОС Android
  • darwin — macOS и iOS (ядро Darwin)
  • ios — операционная система для мобильных устройств Apple
  • linux — дистрибутивы на базе ядра Linux
  • windows — Microsoft Windows
  • freebsd, netbsd, openbsd, dragonfly — операционные системы семейства BSD
  • solaris, illumos — ОС на базе OpenSolaris
  • aix — ОС от IBM для архитектуры Power
  • zos — ОС для мейнфреймов IBM
  • plan9 — распределенная ОС от Bell Labs
  • js — запуск в браузере через JavaScript/WebAssembly (в паре с wasm)

Причем в любой среде выполнения на любом процессоре можно создать файл для любой другой пары процессор/среда.

$ GOOS=windows GOARCH=amd64 go build -o app.exe main.go

Полученный файл будет монолитным — то есть будет содержать все в себе и не нуждаться в других файлах. Его можно просто скопировать на целевую систему и он будет работать. И размер у этого файла будет совсем немаленьким. Хорошо или плохо — но это так.

Модули#

Пакеты#

Функции#

Переменные#

Любое сложное действие можно разделить на более простые, те — на еще более простые и так далее, до базовых машинных команд. Это — декомпозиция, основа всего программирования.

Единицей декомпозиции в Go являются функции. Но не те, что в математике (или, скажем, в языке программирования Haskell). Они получают аргументы и возвращают значение, но если “там” значение зависит только от аргументов и, однажды вычисленное, готовое значение всегда просто повторно используется, то “здесь” это скорее подпрограммы, которые каждый раз выполняются.

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

Несколько функций можно собрать в пакет. Пакет это единица компиляции. Каждый пакет компилируется отдельно, а потом из них, как и кубиков, собирается программа. Единица компиляции – сущность атомарная, в конечную программу попадают не только затронутые функции, а весь пакет целиком. Вся стандартная библиотека состоит из пакетов. Чтобы вызвать функцию пакета, его нужно явным образом импортировать. Так компилятор будет знать, какие пакеты собирать в программу.

Функции пакетов стандартной библиотеки вызывают друг друга и это не составляет проблемы – при обновлении все изменения стандартной библиотеки остаются согласованными. Другое дело сторонние библиотеки, которые могут меняться произвольно. Для этого существуют версии. Каждое изменение в пакете приводит к появлению новой версии пакета – то есть код, использующий определенную версию пакета, не сломается.

Модуль – единица организации кода. В модуль входит один или несколько пакетов. У модуля есть версия и это спасает в ситуациях, когда работающая программа перестает собираться из-за того, что какой-то из пакетов за это время обновится и потерял совместимость. Все сторонние библиотеки поставляются именно в виде модулей – с версиями, набором пакетов и зависимостей.

Программы на Go это тоже модули. От библиотечного модуля программа отличается наличием пакета main с функцией main – именно с нее начинается выполнение собранной программы.

Вторая “первая” программа#

Теперь построим “настоящую” первую программу.

$ mkdir hello
$ cd hello
$ go mod init hello

Появится файл go.mod, который выглядит примерно так:

module hello

go 1.24.6

А теперь – файлы с исходным кодом.

demo1.go

package main

func main() {
	hello()
}

demo2.go:

package main
import "fmt"

func hello() {
	fmt.Println ("Hello, world!")
}

Тут сразу несколько интересных моментов:

hello.go (один пакет можно разбить на несколько файлов, главное – указать общее имя в первой строке)

package main
import "fmt"

func hello_en () {
	fmt.Println ("Welcome!")
}

func hello_de () {
	fmt.Println ("Willkommen!")
}

Создадим вложенный пакет UA

$ mkdir UA
$ cd UA

hitchhiker.go

package hitchhiker
import "fmt"

func Answer() {
	fmt.Println("Ultimate answer is", 42)
}

Компилируем и запускаем:

$ go build
$ ./demo
Welcome!
Willkommen!
Ultimate answer is 42

Поздравляю! Пройден самый крутой участок кривой обучения. По сравнению с ним, все остальное будет просто прогулкой.