Перейти до змісту

Пакети та видимість

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

package store

Усе інше тримається на двох правилах:

  • Один каталог = один пакет. Усі файли .go в каталозі мають оголошувати однакову назву пакета; разом вони утворюють цей пакет.
  • package main особливий — це точка входу виконуваної програми, і він мусить містити func main(). Кожен інший пакет — це бібліотека, яку імпортують інші.

Шляхи імпорту проти назв пакетів

Пакет ви імпортуєте за його шляхом імпорту (його розташуванням), а звертаєтеся до нього за назвою пакета (ідентифікатором в оголошенні package). Зазвичай це останній елемент шляху, але не завжди:

import "example.com/shop/store"   // шлях імпорту

store.Open()                       // використання за назвою пакета

Назва пакета — це те, що з'являється в коді, тож її обирають за тим, як вона читається в місці виклику.

Експортоване проти неекспортованого: керування доступом — це регістр

Ідентифікатор, чия назва починається з великої літери, є експортованим — видимим коду в інших пакетах. З малої — неекспортований — видимий лише всередині свого пакета. Це стосується кожного імені верхнього рівня: типів, функцій, змінних, констант і полів структур.

package store

type Item struct {
    Name  string   // експортоване поле
    price int      // неекспортоване поле
}

func New() *Item    { return &Item{} }   // експортована
func reset(i *Item) { i.price = 0 }       // неекспортована

Межа приватності — це пакет, а не тип чи файл. Файли в одному пакеті вільно бачать неекспортовані імена одне одного; інший пакет бачить лише експортовані. З іншого пакета компілятор це забезпечує:

// у пакеті main, що імпортує пакет store вище
i := store.New()
fmt.Println(i.Name)    // ок — Name експортоване
fmt.Println(i.price)   // compile error: i.price undefined
                       // (cannot refer to unexported field price)

З погляду Python: немає ні суто рекомендаційної домовленості _private, ні спотворення імен __name — видимість забезпечує компілятор, а одиниця приватності — це пакет, а не клас.

Файли в одному пакеті

Розбиття пакета на кілька файлів суто організаційне — файли ділять один простір імен, і порядок оголошень не має значення (функція може викликати іншу, оголошену пізніше, у будь-якому файлі пакета).

// item.go
package store
func New() *Item { return &Item{price: basePrice} }

// price.go
package store
const basePrice = 100      // видима для item.go без жодного імпорту

Функції init

Пакет може оголосити одну або кілька func init() — без параметрів, без результатів. Вони запускаються автоматично під час ініціалізації пакета, після того як налаштовано всі змінні рівня пакета, і перед стартом main. Імпорти пакета ініціалізуються першими, тож на момент запуску вашого init усе, від чого ви залежите, вже готове.

package config

var settings map[string]string

func init() {
    settings = map[string]string{"env": "dev"}
}

Порядок — спершу змінні рівня пакета, потім init, потім main — можна спостерігати:

var x = setup()

func setup() int { fmt.Println("var init"); return 1 }
func init()      { fmt.Println("init func") }
func main()      { fmt.Println("main") }
// output:
// var init
// init func
// main

Користуйтеся init помірно — для налаштування, яке справді неможливо виразити звичайним ініціалізатором змінної. Кілька init (навіть у різних файлах) виконуються в порядку, у якому файли подано компілятору.

Домовленості про назви

  • Назви пакетів короткі, з малих літер, одне словоhttp, bytes, strconv. Без під_креслень чи camelCase.
  • Уникайте затинання. Кваліфікатор пакета вже є в місці виклику, тож називайте bytes.Buffer, а не bytes.BytesBuffer; store.New, а не store.NewItem, коли пакет і так усе пояснює.
  • Док-коментар до пакета — // Package store ... — розміщують над оголошенням package в одному файлі, і він документує весь пакет.
// Package store manages the product catalogue.
package store

Жодних циклів імпорту

Імпорти пакетів мають утворювати ациклічний граф: якщо a імпортує b, то b не може імпортувати a, ні прямо, ні транзитивно. Компілятор відхиляє цикли беззастережно. Коли два пакети ніби потребують одне одного, це знак, що спільний тип має переїхати до третього пакета, який вони обидва імпортують.

// compile error: import cycle not allowed
// package a imports b; package b imports a

Швидка довідка

Поняття Правило
оголошення package кожен файл .go починається з package X
один каталог рівно один пакет
package main + func main() збирає виконувану програму
шлях імпорту де пакет; вживається в import
назва пакета як ви звете його в коді
Uppercase експортоване (видиме іншим пакетам)
lowercase неекспортоване (приватне для пакета)
func init() запускається під час ініціалізації пакета, перед main
цикл імпорту заборонено

Джерела