JavaRush /Курси /Go SELF /Модулі Go з нуля: go mod in...

Модулі Go з нуля: go mod init, go.mod і go.sum

Go SELF
Рівень 35 , Лекція 0
Відкрита

1. Базові поняття та старт модуля

Якщо ви тільки почали, то слово «залежності» звучить як щось із дорослого життя: іпотека, кредити й бібліотека, яка раптово вимагає іншу бібліотеку, а та — третю. Насправді залежності — це просто сторонній код, який ви підключаєте. А модульна система Go створена саме для того, щоб цей процес був керованим: який код, якої він версії, звідки його взяли, і щоб фраза «у мене працює» перетворювалася на «працює в усіх».

Історично в Go існував режим, у якому проєкти жили всередині спеціальної папки GOPATH, а залежності завантажувалися туди ж. Це працювало… доки світ був маленький, а залежності — скромні. Потім з’ясувалося, що без явної фіксації версій проєкт сьогодні може збиратися одним чином, а за місяць — іншим. І це не містика, а звичайне життя.

Тому в Go з’явилися модулі: це одиниця постачання коду з версіонуванням і зрозумілими межами. Підтримку модулів було додано в Go 1.11 як альтернативу GOPATH, з ідеєю вбудованого керування версіями та поширенням пакетів.

Ще один корисний момент: команда go — центральний інструмент в екосистемі, а Go не раз спрощував життя, збираючи розрізнені інструменти в один зрозумілий інтерфейс. І керування модулями — якраз частина цього підходу: менше шаманства, більше відтворюваності.

Що таке модуль і де проходить його межа

Давайте домовимося про терміни так, щоб потім не плутатися. Пакет — це папка з файлами *.go, усередині яких написано package .... А модуль — це набір пакетів, який описано одним файлом go.mod. І ось що важливо: модуль задає межу, у якій Go розуміє, що є вашим проєктом, і як розв’язувати імпорти у вашому коді.

Найпрактичніша аналогія така: модуль — це папка проєкту з паспортом. Паспортом є go.mod. Де лежить паспорт — там і межа: усе, що нижче в дереві папок, належить модулю, якщо далі не трапиться інший go.mod.

Схематично це виглядає так:

todoapp/              <-- корінь модуля (тут лежить go.mod)
  go.mod
  main.go
  todo/
    task.go           <-- пакет todo, теж частина цього ж модуля

І тепер ключовий зв’язок: module path із go.mod стає префіксом шляхів імпорту ваших власних пакетів. Тобто модуль відповідає на запитання: «Як називатиметься ваш проєкт у import?».

Створюємо модуль через go mod init

Коли ви пишете маленьку програму в одному файлі, вам може здаватися, що жодні модулі не потрібні. Але щойно ви робите нормальний проєкт, хоча б із двома пакетами, наявність go.mod робить структуру передбачуваною.

Команда, з якої починається модульне життя:

go mod init <module-path>

Тут <module-path> — це рядок, який стане назвою вашого модуля. У реальних проєктах це зазвичай адреса репозиторію: наприклад, github.com/username/todoapp. У навчальних прикладах часто використовують example.com/..., щоб не прив’язуватися до конкретного хостингу.

Уявімо, що наш навчальний проєкт називається todoapp, і ми хочемо module path:

example.com/todoapp

Тоді після go mod init example.com/todoapp у вас з’являється файл go.mod. Мінімальний скелет виглядає так:


module example.com/todoapp

go 1.22

Зверніть увагу: це не Go-код, а спеціальний формат для інструмента go. Його не «виконують» — його читають команди go build, go test і далі.

3. Файл go.mod: як читати його й не боятися

Файл go.mod часто лякає новачка тим, що виглядає як конфіг, який «страшно чіпати». Гарна новина: базову частину go.mod легко зрозуміти, якщо не намагатися запам’ятати все одразу. На старті вам потрібно вміти читати його й розуміти зміст основних рядків.

Директива module: назва проєкту для import

У go.mod рядок module ... задає module path — кореневий шлях, який далі бере участь в імпортах ваших пакетів.

Якщо у вас структура:

todoapp/
  go.mod     (module example.com/todoapp)
  todo/
    task.go  (package todo)

то шлях імпорту пакета todo буде таким:

example.com/todoapp/todo

І ось тут важливо не переплутати два поняття:

Поняття Приклад Де використовується Що означає
Імʼя пакета
todo
package todo
внутрішня назва в коді
Шлях імпорту
example.com/todoapp/todo
import "..."
адреса, за якою Go знаходить пакет

Директива go: версія мови чи версія інструментів

Рядок виду go 1.22 не означає, що саме таку версію Go потрібно встановити на комп’ютер, хоча зазвичай корисно, щоб версія Go була не нижчою. Це радше вказівка на те, з якою версією поведінки інструментів і мови розрахований модуль.

Для новачка корисно мислити так: цей рядок допомагає інструментам Go поводитися передбачувано під час збірки, тестів і розв’язання залежностей.

Чому в go.mod може не бути require

На самому старті go.mod може складатися лише з module і go. І це не помилка. Директиви require з’являються тоді, коли ви починаєте імпортувати зовнішні пакети, не зі стандартної бібліотеки. Зараз фіксуємо лише ідею: «зовнішня залежність має бути записана в go.mod».

Ми свідомо не заглиблюємося в те, як саме вибирати версії й якими командами це робити. Наша мета — упевнено стартувати з модулем і розуміти його базову структуру.

4. Приклад: todoapp із двома пакетами

Зараз зберемо маленький живий проєкт, щоб ви відчули користь module path на практиці. Ми зробимо два пакети: main і todo. Пакет todo міститиме просту функцію нормалізації заголовка задачі: прибираємо зайві пробіли. Так, це мікрофіча. Але саме з таких мікрофіч потім виростає застосунок, а не «простирадло main.go на 2000 рядків».

Структура папок

Уявімо таку структуру:

todoapp/
  go.mod
  main.go
  todo/
    normalize.go

Код пакета todo

package todo

import (
	"strings"
)

// NormalizeTitle приводить заголовок задачі до акуратного вигляду.
func NormalizeTitle(s string) string {
	return strings.TrimSpace(s)
}

Тут усе знайоме: пакет, імпорт стандартної бібліотеки, функція.

Код main.go з імпортом свого пакета

package main

import (
	"fmt"

	"example.com/todoapp/todo"
)

func main() {
	title := todo.NormalizeTitle("   купити молоко   ")
	fmt.Println(title) // купити молоко
}

Головний момент: імпорт власного пакета йде через module path + підпапка.

Якби go.mod не існувало або лежало не там, Go не зміг би коректно зрозуміти, що таке example.com/todoapp/todo у межах вашого локального диска. А з go.mod усе стає логічним: «це мій модуль, ось його імʼя, отже мої пакети так і імпортуються».

Щоб закріпити логіку, корисно уявити ось таку схему:

flowchart TD
    A[Файл Go: import '...'] --> B{Це stdlib?}
    B -- так --> C[Шукаємо в GOROOT: fmt, strings, ...]
    B -- ні --> D{Починається з module path?}
    D -- так --> E[Шукаємо пакет у межах поточного модуля]
    D -- ні --> F[Шукаємо у зовнішніх модулях за залежностями]

Саме тому module path — не «формальність», а ключ до того, щоб Go розумів, де свої, а де — чужі.

5. go.sum і відтворюваність

Навіщо потрібен go.sum

Тепер про файл, який часто викликає реакцію: «Хто це зробив і чому це взагалі потрапляє до репозиторію?». Йдеться про go.sum. Якщо go.mod — це декларація «які модулі та яких версій потрібні», то go.sum — це сліди перевірки: чи той самий код ми завантажили, який очікуємо.

Важливо почати з правильного очікування: go.sum зазвичай не створюють руками. Він з’являється, коли інструменти Go вперше підтягують зовнішні залежності, і далі підтримується автоматично. І так, найчастіше його справді комітять у репозиторій, бо це частина відтворюваної збірки: один і той самий проєкт на двох машинах має отримувати один і той самий перевірений набір вихідних кодів.

У Go 1.16, наприклад, окремо підкреслювалося, що команди в модульному режимі не повинні «тихо» змінювати go.mod і go.sum самі по собі: якщо є проблема із залежностями, інструмент повідомляє про помилку і підказує, що робити, замість несподіваних автополагоджень. Це про передбачуваність: менше сюрпризів, більше контролю.

А ще з Go 1.16 модулі стали режимом за замовчуванням в інструментарії, тож екосистема остаточно закріпилася навколо модульної моделі.

Як виглядає go.sum

Фрагмент може виглядати так, схематично:

golang.org/x/net v0.20.0 h1:...
golang.org/x/text v0.14.0 h1:...

Не намагайтеся читати це як роман. Це «контрольні суми» — перевірочні відбитки модулів, потрібні інструментам.

Практичне правило: якщо ви бачите зміни в go.sum, це майже завжди означає одне з двох: або додали чи оновили залежність, або інструменти перебудували чи уточнили граф залежностей. У будь-якому разі це не той файл, який ви редагуєте в блокноті «для краси».

Коротка ментальна модель модулів

Коли модульна система вкладається в голову, вона перестає дратувати. Тому зафіксуймо просту модель без зайвої філософії.

Ваш проєкт у Go — це папка, у якій лежить go.mod. Цей файл задає ім’я модуля (module path) і правила для інструментів. Усередині проєкту ви створюєте пакети як підпапки й імпортуєте їх через module path + шлях до підпапки.

Якщо ви імпортуєте не стандартну бібліотеку й не власний пакет, значить це зовнішній модуль, і він має бути зафіксований у залежностях. Для відтворюваності та перевірки цілісності інструмент веде go.sum.

Якщо ви впіймали себе на думці «я не розумію, звідки Go бере цей пакет», майже завжди відповідь починається з фрази: «А де в тебе go.mod, і що в ньому написано в module?».

6. Типові помилки

Помилка № 1: go.mod лежить не в корені, а у випадковій папці.
Таке часто буває, коли людина створила проєкт, потім зробила ще одну папку, потім ще одну, а go.mod опинився на другому поверсі цього лабіринту. У результаті межа модуля стає несподіваною: одні пакети «видимі», інші — ні, а імпорти починають виглядати як магія. Лікується просто: свідомо вибрати корінь проєкту й тримати go.mod саме там.

Помилка № 2: плутанина між package name та import path.
Новачки іноді пишуть у import щось на кшталт "todo" і дивуються, чому не працює. Імʼя пакета (package todo) — це внутрішня назва в коді. А шлях імпорту — це адреса, за якою Go його знаходить. Для своїх пакетів він зазвичай починається з module path із go.mod.

Помилка № 3: бажання «перейменувати модуль» без розуміння наслідків.
Рядок module ... — це не просто підпис. Якщо ви змінили module path, вам, найімовірніше, доведеться змінити імпорти власних пакетів по всьому проєкту. Тому до зміни module ставляться обережно: це можливо, але це не косметика.

Помилка № 4: спроба редагувати go.sum вручну.
go.sum виглядає як незрозумілий набір рядків, і руки так і тягнуться «підчистити сміття». Але сенс файла — у машинній перевірці цілісності. Якщо правити його вручну, ви зазвичай не «покращуєте проєкт», а створюєте собі майбутню загадкову помилку збірки. Правильний підхід: вважати go.sum артефактом інструментів і довіряти команді go.

Помилка № 5: очікування, що модуль — це лише про зовнішні бібліотеки.
Насправді модуль потрібен уже для того, щоб ваші власні імпорти були стабільними й зрозумілими. Навіть якщо у вас нуль зовнішніх залежностей, go.mod усе одно корисний як «паспорт проєкту» і точка відліку для import path.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ