Урок 10. Пакеты и модули
Пока программа помещается в один файл, о пакетах можно не думать. Но уже в небольшом проекте удобно разложить код по частям: отдельно работа с базой, отдельно HTTP-обработчики, отдельно расчёты. В Go такие части называются пакетами, а весь проект — модулем.
Пакет
Пакет — папка с файлами .go. Первая строка каждого файла — package имя, и у всех файлов одной папки имя одно. Внутри пакета файлы видят функции и переменные друг друга, как будто это один файл.
Имя пакета — короткое, строчными буквами, без _: greet, storage, config. Обычно оно совпадает с именем папки. Пакет main особый: из него собирается программа.
Снаружи пакета видны только имена с большой буквы — это и есть экспорт. Писать public и private, как в других языках, не нужно.
Модуль
Модуль — папка с файлом go.mod вместе со всеми вложенными пакетами. Файл создаёт go mod init:
module example.com/hello
go 1.22В первой строке — путь модуля, во второй — версия Go, под которую написан код (у вас будет номер вашей версии). Путь модуля — начало адреса всех его пакетов: пакет из папки greet импортируется как example.com/hello/greet.
Свой пакет: пример
Структура проекта:
hello/
go.mod
main.go
greet/
greet.goФайл greet/greet.go:
package greet
import "fmt"
// Hello возвращает приветствие для name.
func Hello(name string) string {
return fmt.Sprintf("Привет, %s!", name) + suffix()
}
func suffix() string {
return " Добро пожаловать."
}Файл main.go — его запускают из папки hello командой go run .:
package main
import (
"fmt"
"example.com/hello/greet"
)
func main() {
fmt.Println(greet.Hello("Анна"))
}Привет, Анна! Добро пожаловать.- Пакет подключают по полному пути:
"example.com/hello/greet", а в коде обращаются по имени:greet.Hello. Helloначинается с большой буквы — её видно изmain.suffix— с маленькой, вызватьgreet.suffix()изmainнельзя.- Комментарий перед экспортируемой функцией начинают с её имени — из таких комментариев строится документация.
- Стандартные пакеты в
importобычно пишут первой группой, свои и чужие — второй, через пустую строку.
Если путь в import не совпадает с путём модуля из go.mod, сборка сообщит package … is not in std или no required module provides package …. Сверьте первую строку go.mod и путь в import.
Чужие пакеты
Готовые библиотеки ищут на pkg.go.dev — там же их документация. Подключить пакет можно двумя способами. Первый — сразу скачать нужный:
go get rsc.io/quoteВторой — сначала написать import в коде, а потом выполнить go mod tidy: команда сама найдёт и скачает всё, что импортируется, и уберёт из go.mod то, что больше не нужно. Пример из официального учебника Go — после go mod tidy программа запускается как обычно, go run .:
package main
import (
"fmt"
"rsc.io/quote"
)
func main() {
fmt.Println(quote.Go())
}Don't communicate by sharing memory, share memory by communicating.После этого в go.mod появятся строки require с версиями пакетов, а рядом — файл go.sum с контрольными суммами (короткими «отпечатками» содержимого: по ним видно, что пакет не изменился). Оба файла хранят вместе с кодом (в git): по ним любой, кто соберёт проект, получит ровно те же версии библиотек.
Подключайте пакеты, которые знаете: известные, с документацией и живым репозиторием. Чужой код выполняется в вашей программе со всеми её правами.
Стандартная библиотека
Многое есть в Go сразу, без внешних пакетов: strings и strconv — строки и числа, os — файлы и переменные окружения, time — время, net/http — HTTP-сервер и клиент, encoding/json — JSON, database/sql — базы данных (драйвер конкретной базы ставится отдельно). Пример с JSON:
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
data, err := json.Marshal(User{Name: "Анна", Age: 20})
if err != nil {
fmt.Println("ошибка:", err)
return
}
fmt.Println(string(data))
var u User
err = json.Unmarshal([]byte(`{"name":"Борис","age":31}`), &u)
if err != nil {
fmt.Println("ошибка:", err)
return
}
fmt.Printf("%+v\n", u)
}{"name":"Анна","age":20}
{Name:Борис Age:31}Строки в обратных кавычках после полей — теги: они задают имена полей в JSON. encoding/json видит только поля с большой буквы: поле name string в JSON не попало бы. Строка в обратных кавычках — «сырая»: в ней не нужно экранировать кавычки (ставить перед ними \), поэтому JSON в примере записан как есть.
Команды на каждый день
| Команда | Что делает |
|---|---|
go run . | собрать и запустить программу из текущей папки |
go build | собрать исполняемый файл |
go build ./... | проверить, что собираются все пакеты модуля |
go fmt ./... | отформатировать весь код |
go vet ./... | найти подозрительные места: неверный формат Printf, копирование WaitGroup и другие |
go test ./... | запустить тесты (файлы *_test.go) |
go mod tidy | привести go.mod и go.sum в соответствие с import |
go install путь@latest | поставить программу на Go в $HOME/go/bin |
./... значит «текущая папка и все вложенные».
Подводные камни
- Циклический импорт. Если пакет
aимпортируетb, аb—a, сборка остановится с ошибкойimport cycle not allowed. Общий код выносят в третий пакет. - Папка internal. Пакеты внутри папки
internalможно импортировать только из кода того же модуля, лежащего выше неё. Так прячут то, что не предназначено для чужих проектов. - Неиспользуемый импорт — ошибка компиляции: imported and not used.
- go.mod не найден — команда запущена не в папке модуля: go mod init и go.mod file not found.
Коротко
- Пакет — папка с файлами
.goс одинаковымpackage имя; модуль — папка сgo.modи всеми пакетами внутри. - Импорт своего пакета — путь модуля плюс папка:
example.com/hello/greet. - Экспорт — большая первая буква имени.
- Чужие пакеты —
go getилиimportиgo mod tidy; версии — вgo.mod, суммы — вgo.sum. go build ./...,go vet ./...,go test ./...— проверить весь модуль.