Menu

Package dan Import di Golang: Nama yang Di-export, Banyak File

Cara kerja package di Go: package clause, mengimpor package standar dan package dari module, nama yang di-export dan yang tidak, memecah package ke beberapa file, package internal, alias import, blank import, dan error "undefined" dari go run main.go.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Setiap file Go termasuk ke dalam sebuah package, yang dideklarasikan di baris pertamanya. Kode di satu package memakai package lain dengan mengimpornya dan memberi prefix nama package pada nama-nama di dalamnya. Hanya nama yang diawali huruf kapital yang bisa dipakai dari luar package.

fmt, math, dan strings adalah package standard library. Semuanya sudah ikut terpasang bersama Go, jadi "menambahkan" salah satunya berarti menulis namanya di blok import. Tidak ada yang diunduh. fmt.Println, math.Sqrt, dan strings.Repeat diawali huruf kapital karena di-export.

Package clause

Baris pertama yang bukan komentar di setiap file .go adalah package name. Semua file di satu direktori harus memakai nama yang sama, dan bersama-sama membentuk satu package. Direktori dengan dua nama package berbeda tidak bisa dikompilasi:

found packages greeting (b.go) and greet (greet.go) in /home/ana/shop/greet

(Satu-satunya pengecualian adalah file test, yang boleh memakai package name_test.)

package main istimewa: package ini menghasilkan program yang bisa dieksekusi, dan program dimulai dari func main()-nya. Package lainnya adalah library yang dimaksudkan untuk diimpor.

Menurut konvensi, nama package sama dengan elemen terakhir path direktorinya: kode di shop/greet adalah package greet. Nama package itu pendek, berhuruf kecil, satu kata, tanpa garis bawah atau campuran huruf kapital: strconv, httptest, greet, bukan string_utils atau greetHelpers.

Mengimpor package

Import path adalah sebuah string. Untuk standard library, isinya adalah path package di dalam source tree Go; untuk yang lain, diawali dengan path module:

import (
	"fmt"           // standard library
	"net/http"      // standard library, nested path
	"math/rand/v2"  // standard library, version 2 of math/rand

	"github.com/google/uuid"   // another module (needs go get)
	"example.com/shop/greet"   // a package in your own module
)

Path adalah tempat package berada; nama adalah apa yang kamu ketik di kode. Untuk net/http namanya http, untuk math/rand/v2 namanya rand. Kamu menulis http.Get, tidak pernah net/http.Get.

gofmt dan goimports mengurutkan blok import, dan menurut konvensi memisahkan import standard library dari yang lain dengan satu baris kosong.

Import yang tidak dipakai adalah compile error:

./main.go:5:2: "os" imported and not used

Editor yang mendukung Go menjalankan goimports saat menyimpan, yang menambahkan import standar yang hilang dan menghapus yang tidak dipakai untukmu.

Nama yang di-export dan yang tidak

Visibilitas di Go ditentukan oleh satu aturan: kalau sebuah nama diawali huruf kapital, nama itu di-export. Aturan ini berlaku untuk fungsi, tipe, variabel, konstanta, field struct, dan method. Tidak ada kata kunci public, private, atau protected.

NamaTerlihat dari luar package?
func Hello()ya
func hello()tidak
type User structya
field User.Nameya
field User.emailtidak
const MaxSizeya
var defaultTimeouttidak

Mencoba memakai nama yang tidak di-export dari package lain menghasilkan error undefined:

./main.go:11:20: undefined: greet.prefix

Aturan ini tetap penting bahkan di dalam satu file, karena package lain memeriksa tipe-tipemu. encoding/json hanya bisa melihat field yang di-export:

token tidak dimasukkan ke JSON, tanpa peringatan apa pun. Ini fitur yang berguna saat field-nya berisi data privat, dan kejutan saat kamu lupa mengawali dengan huruf kapital field yang kamu inginkan.

Memecah package ke beberapa file

Sebuah package bisa terdiri dari file sebanyak yang kamu mau. Setiap file di direktori dengan baris package yang sama bisa melihat setiap nama yang dideklarasikan di file lain, di-export atau tidak, tanpa mengimpor apa pun:

shop/
├── go.mod        (module example.com/shop)
├── main.go
└── math.go
// main.go
package main

import "fmt"

func main() {
	fmt.Println(total(2, 3))
}
// math.go
package main

func total(a, b int) int { return a + b }

main.go memanggil total secara langsung. Tidak ada #include dan tidak ada import antarfile dalam package yang sama.

Error "undefined" dari go run main.go

Ini masalah package yang paling sering dicari di Go, dan penyebabnya hanya satu. Menjalankan file yang berisi main saja hanya mengompilasi file itu:

go run main.go
# command-line-arguments
./main.go:6:14: undefined: total

command-line-arguments adalah nama yang diberikan Go untuk package yang di-build dari daftar file, dan math.go tidak ada di daftar itu. Jalankan seluruh package sebagai gantinya:

go run .
5

Hal yang sama berlaku untuk go build main.go. Pakai go run . dan go build (atau go build ./cmd/app), maka masalah ini tidak akan pernah muncul. Kalau masih terjadi dengan go run ., pastikan kedua file menulis package main dan tidak ada yang dikecualikan oleh build constraint (//go:build) atau akhiran _test.go.

Package buatanmu sendiri

Subdirektori di dalam module-mu adalah package tersendiri. Impor dengan path module dari go.mod ditambah direktorinya:

shop/
├── go.mod          (module example.com/shop)
├── main.go
└── greet/
    └── greet.go
// greet/greet.go
package greet

const prefix = "Hi, "

// Hello returns a greeting for name.
func Hello(name string) string {
	return prefix + name
}
// main.go
package main

import (
	"fmt"

	"example.com/shop/greet"
)

func main() {
	fmt.Println(greet.Hello("Ana"))
}
Hi, Ana

Ada dua hal yang sering menjebak di sini. Import-nya adalah path module ditambah direktori, bukan path relatif: "./greet" tidak bisa dipakai di mode module. Dan prefix tetap tersembunyi dari main karena berhuruf kecil; hanya Hello yang menjadi bagian dari API package itu.

Package internal

Direktori bernama internal membatasi siapa yang boleh mengimpor isinya. Kode di shop/internal/store bisa diimpor oleh package apa pun yang berakar di shop/, dan tidak oleh apa pun di luarnya. Module lain yang mencobanya mendapat:

use of internal package example.com/shop/internal/store not allowed

Pakai internal/ untuk kode yang ingin kamu bagikan di antara package-mu sendiri tanpa menjanjikannya ke seluruh dunia. Kamu bebas mengubah API-nya, karena tidak ada kode dari luar yang bisa bergantung padanya.

Alias import

Beri import nama lokal yang berbeda dengan menulis nama itu sebelum path-nya. Alasan yang biasa adalah dua package dengan nama yang sama:

Kedua package bernama rand, jadi setidaknya salah satunya butuh alias. Pakai alias untuk konflik nama dan untuk nama yang tidak jelas; jangan mengganti nama package hanya supaya lebih pendek, karena pembaca mengenal strconv, bukan sc buatanmu.

Blank import dan dot import

Blank import (_). Mengimpor package hanya untuk efek sampingnya: variabel package dan fungsi init-nya dijalankan, tapi kamu tidak memakai satu pun namanya. Driver database dan decoder gambar mendaftarkan diri dengan cara ini:

import (
	"database/sql"

	_ "github.com/lib/pq" // registers the "postgres" driver with database/sql
)

Tanpa _, import itu akan menjadi error import yang tidak dipakai.

Dot import (.). Memasukkan nama-nama yang di-export dari package langsung ke scope file-mu, jadi kamu menulis Println, bukan fmt.Println. Ini menyembunyikan asal nama-nama itu dan tidak disarankan, kecuali di beberapa pola helper untuk test.

Import cycle

Go tidak mengizinkan dua package saling mengimpor, baik secara langsung maupun lewat rantai:

package example.com/shop
	imports example.com/shop/greet from main.go
	imports example.com/shop/other from c.go
	imports example.com/shop/greet from o.go: import cycle not allowed

Error-nya menampilkan rantainya. Dua perbaikan ini mencakup hampir semua kasus:

  1. Pindahkan kode yang dibutuhkan kedua package ke package ketiga yang levelnya lebih rendah, yang diimpor oleh keduanya.
  2. Kalau package A hanya perlu memanggil sesuatu di B, definisikan interface kecil di A dan biarkan tipe milik B memenuhinya. A tidak lagi mengimpor B.

Cycle biasanya berarti batas package digambar di sekitar lapisan kode yang sebenarnya satu kesatuan, jadi ini juga pertanda untuk memikirkan ulang pembagiannya.

Kesalahan umum

  • Menjalankan satu file saja. go run main.go di package yang terdiri dari banyak file: pakai go run ..
  • Import relatif. import "./utils" gagal di mode module. Pakai "example.com/yourmodule/utils".
  • Nama berhuruf kecil yang sebenarnya ingin kamu export. Fungsi, field, atau method yang dibutuhkan package lain harus diawali huruf kapital.
  • Nama package yang tidak sama dengan direktorinya. Sah, tapi membingungkan: import path-nya menyebut utils sementara kodenya menyebut helpers.X. Samakan keduanya.
  • Nama package serba guna. util, common, dan misc tidak mengatakan apa pun tentang isinya dan cenderung tumbuh menjadi magnet import cycle. Namai package sesuai apa yang disediakannya: money, auth, slug.

Pertanyaan yang sering diajukan

Kenapa Go bilang fungsi di package yang sama undefined?

Kemungkinan kamu menjalankan go run main.go. Menyebut nama file hanya mengompilasi file itu, jadi fungsi yang didefinisikan di file lain dalam package yang sama tidak ikut: undefined: total. Jalankan go run . sebagai gantinya, yang mengompilasi setiap file .go di direktori. Periksa juga bahwa setiap file punya baris package yang sama.

Bagaimana cara menambahkan package fmt di Go?

fmt adalah bagian dari standard library dan sudah ikut terpasang bersama Go, jadi tidak ada yang perlu diinstal. Tambahkan import "fmt" setelah baris package lalu panggil fungsinya dengan prefix fmt., misalnya fmt.Println("hi"). Hal yang sama berlaku untuk strings, os, time, net/http, dan setiap package standar lainnya.

Bagaimana cara mengimpor package buatan sendiri di Go?

Impor dengan path module ditambah direktori. Di module bernama example.com/shop, package di folder greet diimpor sebagai import "example.com/shop/greet" dan dipakai sebagai greet.Hello(). Hanya nama yang diawali huruf kapital yang terlihat oleh pengimpor.

Apa perbedaan exported dan unexported di Go?

Nama yang diawali huruf kapital (Hello, Total, User.Name) di-export dan bisa dipakai dari package lain. Nama berhuruf kecil (hello, total, user.name) tidak di-export dan hanya terlihat di dalam package-nya sendiri. Tidak ada kata kunci public atau private.

Bagaimana cara mengatasi "import cycle not allowed" di Go?

Dua package saling mengimpor, secara langsung atau lewat rantai, dan Go melarangnya. Pindahkan kode yang dipakai bersama ke package ketiga yang diimpor keduanya, atau definisikan interface kecil di package yang membutuhkan perilaku itu supaya tidak perlu lagi mengimpor package satunya.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai