Глиф Учебники

Урок 10. Пакеты и модули

Курс «Go» · урок 10 из 10 · 5 минут чтения

Пока программа помещается в один файл, о пакетах можно не думать. Но уже в небольшом проекте удобно разложить код по частям: отдельно работа с базой, отдельно 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:

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 .:

Go
package main

import (
    "fmt"

    "example.com/hello/greet"
)

func main() {
    fmt.Println(greet.Hello("Анна"))
}
Вывод
Привет, Анна! Добро пожаловать.
package greet видна снаружи func Hello только внутри func suffix package main greet.Hello("Анна") можно: имя с большой буквы greet.suffix() нельзя: имя с маленькой буквы
Из пакета main видна функция Hello, потому что её имя начинается с большой буквы, а suffix с маленькой буквы вызывается только внутри пакета greet.

Если путь в import не совпадает с путём модуля из go.mod, сборка сообщит package … is not in std или no required module provides package …. Сверьте первую строку go.mod и путь в import.

hello/ go.mod example.com/hello main.go package main greet/ greet.go package greet модуль — папка с go.mod и всем внутри путь модуля папка example.com/hello + /greet import "example.com/hello/greet" greet.Hello("Анна") в коде — по имени пакета
Путь импорта пакета складывается из пути модуля example.com/hello из go.mod и папки greet, а в коде к пакету обращаются по его имени: greet.Hello.

Чужие пакеты

Готовые библиотеки ищут на pkg.go.dev — там же их документация. Подключить пакет можно двумя способами. Первый — сразу скачать нужный:

Bash
go get rsc.io/quote

Второй — сначала написать import в коде, а потом выполнить go mod tidy: команда сама найдёт и скачает всё, что импортируется, и уберёт из go.mod то, что больше не нужно. Пример из официального учебника Go — после go mod tidy программа запускается как обычно, go run .:

Go
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): по ним любой, кто соберёт проект, получит ровно те же версии библиотек.

main.go import "rsc.io/quote" go mod tidy скачивает и записывает go.mod версии require rsc.io/quote v1.5.2 … go.sum контрольные суммы rsc.io/quote v1.5.2 h1:… rsc.io/quote v1.5.2/go.mod h1:… оба файла хранят вместе с кодом: у любого, кто соберёт проект, — те же версии и тот же код
go mod tidy находит в import пакет rsc.io/quote, скачивает его и записывает версию в go.mod, а контрольные суммы — в go.sum; оба файла хранят вместе с кодом.

Подключайте пакеты, которые знаете: известные, с документацией и живым репозиторием. Чужой код выполняется в вашей программе со всеми её правами.

Стандартная библиотека

Многое есть в Go сразу, без внешних пакетов: strings и strconv — строки и числа, os — файлы и переменные окружения, time — время, net/http — HTTP-сервер и клиент, encoding/json — JSON, database/sql — базы данных (драйвер конкретной базы ставится отдельно). Пример с JSON:

Go
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

./... значит «текущая папка и все вложенные».

Подводные камни

Коротко

Проверьте себя

1. Как импортировать пакет из папки storage модуля example.com/shop?
2. Функцию parse из пакета config не удаётся вызвать из main. Почему?
3. Что делает go mod tidy?
4. Зачем хранить go.sum вместе с кодом?