Совсем другая книга про Go
Введение Ссылка на заголовок
«Маленькие книги» Карла Сегуина – лучшие в жанре «быстрого старта». Только самое главное, без подробностей, в которых так легко утонуть. Зато потом упущенное очень легко восполняется правильно заданными вопросами и самостоятельно найденными ответами.
Когда-то «Маленькая книга про Go» вдохновила меня написать несколько других «маленьких книг» на темы, для которых книг Карла не было. А потом, когда я сам пришел к Go и вернулся к этой книге, вдруг оказалось, что многое изменилось. Это будет совсем другая книга про Go. Не судите строго.
Язык программирования Go Ссылка на заголовок
Go это «C++ для инженеров». Пока адепты «поэзии кода» ищут идеальный баланса абстракций и производительности, инженеры делают работающее решение и уходят домой. В Google нуждались в простом и эффективном инструменте для быстрой разработки и внедрения производительных, надежных и безопасных систем. В итоге получился компактный компилируемый язык со строгой типизацией, автоматическим управлением памятью, встроенной поддержкой многозадачности, модульной организацией кода и собственным менеджером пакетов, дающем доступ к большой библиотеке готовых решений, позволяющих даже новичку быть достаточно эффективным.
Это больше, чем декларация. Это философия, которую идеально иллюстрирует шутка «прибор должен работать в корпусе, а не в принципе». И придерживаться этой философии при написании программ – самый лучший способ быть эффективным. Это скучнее, чем писать на С++ или Rust, но хорошего там тоже очень много.
Кроме кривой обучения. В качестве первого языка он не то, чтобы сложен, но… с самого начала потребуется знать то, что обычно изучается в последнюю очередь. В сущности, изучать его приходится в обратном порядке – от сложного к простому. Это настоящий вызов, но я попробую. Опять же, не судите строго.
Первая программа Ссылка на заголовок
У пресловутого “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 hello2
$ mkdir hello2/en
$ mkdir hello2/de
$ cd hello2
$ go mod init hello
Появится файл go.mod, который выглядит примерно так:
module hello
go 1.24.6
Это модуль. Пока пустой.
А теперь – файлы с исходным кодом.
demo1.go
package main
import (
"hello/de"
"hello/en"
)
func main() {
hello()
answer()
}
func hello() {
en.Hello()
de.Hallo()
}
demo2.go:
package main
import "fmt"
func answer() {
fmt.Println("The Answer to the Ultimate Question is ", 42)
}
en/hello.go:
package en
import "fmt"
func Hello() {
fmt.Println("Hello, world")
}
de/hallo.go:
package de
import "fmt"
func Hallo() {
fmt.Println("Hallo Welt")
}
Тут появляется сразу много интересного:
- Один пакет можно разбить на несколько файлов, главное — указать общее имя в первой строке (
package ...). - Называть файлы, директории, пакеты и иодули можно как угодно. Но все же имя модуля и имя директории лучше бы совпадало.
- На самом деле
printlnэтоfmt.Println, фукнция из библиотекиfmt. - Имена фукнций, которые вызываются извне, должны начинаться с большой буквы, это правила области видимости.
- Создавая вложенные модули, можно обойтись без
go mod…, но имя модуля должно совпадать с именем директории.
Компилируем и запускаем:
$ go build
$ ./hello
Hello, world
Hallo Welt
The Answer to the Ultimate Question is 42
Поздравляю! Пройден самый крутой участок кривой обучения. По сравнению с ним, все остальное просто прогулка.