Program Go yang kita tulis sejauh ini masih berupa satu berkas main.go. Begitu program bertumbuh, mempertahankan seluruh kode dalam satu berkas membuat pembacaan dan pengujian menjadi sulit. Go mengatur kode dalam unit bernama package yang dijalin lewat pernyataan import. Pada chapter ini kita akan membedah hubungan direktori dengan package, pembentukan import path, perbedaan package main dengan package library, serta alasan import cycle ditolak compiler.

Package Adalah Direktori #

Aturan paling mendasar di Go: satu package tinggal di satu direktori, dan satu direktori hanya boleh memuat satu package. Semua berkas .go di direktori yang sama wajib menuliskan nama package identik pada baris pertamanya. Nama package secara konvensi sama dengan nama direktorinya, ditulis huruf kecil (contoh: penyapa, bukan Penyapa).

go
package penyapa

func Salam(nama string) string {
	return "Halo, " + nama + "!"
}

Fragmen di atas memperlihatkan bentuk awal berkas penyapa/penyapa.go. Klausa package penyapa menjadikan seluruh fungsi di berkas ini — termasuk Salam — anggota package penyapa. Berkas lain di direktori yang sama wajib menuliskan klausa yang sama; nama yang berbeda memicu galat kompilasi. Dengan begitu, struktur direktori menjadi peta pembagian kode.

Import Path: Module Path Plus Subdirektori #

Ketika kita menulis import "fmt", compiler mencari package fmt di standard library. Untuk package milik module sendiri, import path dibentuk dari module path di go.mod ditambah subdirektori package. Module contoh.com/katalog dengan direktori penyapa menghasilkan import path contoh.com/katalog/penyapa.

Formula ini berlaku konsisten untuk module lokal maupun publik:

Asal packageImport path
Standard libraryfmt, strings, net/http
Module contoh.com/katalog, direktori penyapacontoh.com/katalog/penyapa
Module publik github.com/google/uuid, paket utamagithub.com/google/uuid
Module publik dengan subdirektori driver/mysqlgithub.com/go-sql-driver/mysql

Import path selalu absolut dari akar module; path relatif seperti ./penyapa tidak bisa dipakai. Go memilih model ini agar satu import path selalu merujuk package yang sama di mana pun dipakai.

Package main dan Package Library #

Go membedakan dua peran package. Package bernama main adalah program utuh: compiler menghasilkan berkas biner dengan titik masuk fungsi main(). Package dengan nama lain adalah library yang tidak bisa dieksekusi langsung, tetapi dipakai package lain. Konsekuensinya, package main sebaiknya dijaga tipis — cukup membaca argumen dan memanggil fungsi library.

Bundle chapter ini berisi module kecil src/ yang memperlihatkan kedua peran tersebut dalam satu struktur:

text
src/
├── go.mod              ← module contoh.com/katalog
├── main.go             ← package main (program)
└── penyapa/
    └── penyapa.go      ← package penyapa (library)

Berkas src/main.go memanggil fungsi dari package penyapa:

go
package main

import (
	"fmt"

	"contoh.com/katalog/penyapa"
)

func main() {
	fmt.Println(penyapa.Salam("Budi"))
	penyapa.Tampilkan("Siti")
}

Sementara src/penyapa/penyapa.go berisi dua fungsi library yang menerima dan mengembalikan string:

go
package penyapa

import "fmt"

// Salam mengembalikan sapaan utuh untuk nama yang diberikan.
func Salam(nama string) string {
	return "Halo, " + nama + "!"
}

// Tampilkan mencetak sapaan ke standard output.
func Tampilkan(nama string) {
	fmt.Println(Salam(nama))
}

Karena go.mod sudah ada di dalam src/, kita menjalankan program dari dalam direktori tersebut:

bash
cd src && go run .

Output yang Dihasilkan:

text
Halo, Budi!
Halo, Siti!

Mari kita telaah baris demi baris kedua berkas di atas:

  1. main.go baris 1 (package main): menandai berkas ini sebagai program yang dapat dieksekusi.
  2. main.go baris 3-7 (blok import): mengimpor standard library fmt dan package lokal contoh.com/katalog/penyapa dalam satu blok kurung.
  3. main.go baris 10-11: memanggil Salam dan Tampilkan dari package penyapa melalui nama package sebagai prefiks.
  4. penyapa.go baris 1 (package penyapa): menempatkan kedua fungsi ke package penyapa, bukan main, sehingga tidak dapat dieksekusi langsung.
  5. penyapa.go baris 6-8 (func Salam): fungsi publik yang hanya mengembalikan string tanpa efek samping — ciri library yang mudah diuji.

Import Cycle Ditolak Compiler #

Ketika package A mengimpor package B, sementara B juga mengimpor A, terbentuklah import cycle. Go menolak hubungan melingkar ini sejak kompilasi karena urutan inisialisasi kedua package tidak dapat ditentukan. Berikut hasil kompilasi nyata dua package yang saling mengimpor (demo dijalankan di direktori sementara):

text
package contoh.com/siklus
	imports contoh.com/siklus/a from main.go
	imports contoh.com/siklus/b from a.go
	imports contoh.com/siklus/a from b.go: import cycle not allowed

Pesan tersebut menelusuri rantai impor dari titik masuk: main → a → b → kembali ke a. Solusinya: pindahkan tipe atau fungsi bersama ke package ketiga yang lebih rendah sehingga arah dependensi selalu satu arah.

Dengan memahami aturan direktori-package dan import path, fondasi kita siap membaca berkas go.mod secara tuntas — mulai dari go mod init hingga directive module, go, dan toolchain — pada chapter go.mod dan Module .

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

src/main.go

Unduh berkas
go
package main

import (
	"fmt"

	"contoh.com/katalog/penyapa"
)

func main() {
	fmt.Println(penyapa.Salam("Budi"))
	penyapa.Tampilkan("Siti")
}

src/penyapa/penyapa.go

Unduh berkas
go
package penyapa

import "fmt"

// Salam mengembalikan sapaan utuh untuk nama yang diberikan.
func Salam(nama string) string {
	return "Halo, " + nama + "!"
}

// Tampilkan mencetak sapaan ke standard output.
func Tampilkan(nama string) {
	fmt.Println(Salam(nama))
}