Sintaks Utama #
Sebelum menulis baris kode Go pertama, ada baiknya memahami “bahasa dalam bahasa” — yaitu aturan-aturan yang membentuk cara setiap program Go ditulis. Beberapa aturan ini terasa unik dibanding bahasa lain, dan memahami mengapa aturan itu ada (bukan hanya apa aturannya) akan membuatmu jauh lebih cepat produktif. Artikel ini membahas anatomi program Go dari level paling atas: bagaimana file diorganisir, apa saja komponen wajib, dan mengapa Go membuat keputusan desain yang seringkali berbeda dari ekspektasi developer.
Tiga Komponen Wajib Program Go #
Setiap file Go yang bisa dieksekusi memiliki tiga komponen wajib yang harus muncul dalam urutan ini: deklarasi package, import, dan fungsi main. Tidak ada ketiganya — tidak ada program Go yang bisa jalan.
package main // Komponen 1: deklarasi package
import "fmt" // Komponen 2: import dependency
func main() { // Komponen 3: entry point
fmt.Println("Hello, Go!")
}
Ini bukan sekadar boilerplate. Setiap komponen punya peran yang spesifik dalam cara Go mengorganisir dan mengkompilasi kode.
Sistem Package — Unit Organisasi Kode Go #
Package adalah unit dasar organisasi kode di Go. Setiap file Go harus menjadi bagian dari sebuah package, dan setiap package terdiri dari satu atau lebih file .go dalam satu direktori.
Deklarasi Package #
package main // Package yang menghasilkan executable
package mathutil // Package library
package config // Package library
package httpmiddleware // Package library
Package main adalah satu-satunya package yang bisa dieksekusi langsung — Go mencari fungsi main() di dalamnya sebagai titik masuk program. Semua package lain adalah library yang hanya bisa di-import oleh package lain.
Nama package mengikuti konvensi:
// ✓ Nama package: lowercase, singkat, tanpa underscore
package strings
package http
package json
package mathutil
// ✗ Hindari
package StringUtils // jangan PascalCase
package math_util // jangan underscore
package myVeryLongPackageName // jangan terlalu panjang
Hubungan Package dengan Direktori #
Di Go, satu direktori = satu package. Semua file .go dalam satu direktori harus punya deklarasi package yang sama (dengan pengecualian _test untuk test file).
myproject/
├── main.go → package main
├── go.mod
├── config/
│ ├── config.go → package config
│ └── loader.go → package config (sama!)
├── handler/
│ ├── user.go → package handler
│ └── product.go → package handler (sama!)
└── repository/
├── user_repo.go → package repository
└── db.go → package repository (sama!)
Cara memanggil fungsi dari package lain:
package main
import "myproject/config"
func main() {
cfg := config.Load() // NamaPackage.NamaFungsi
_ = cfg
}
Import — Menggunakan Package Lain #
import memberitahu Go package mana yang dibutuhkan file ini. Go membedakan tiga jenis package: standard library, modul pihak ketiga, dan modul internal.
Import Tunggal dan Multiple #
// Import satu package
import "fmt"
// Import banyak package — cara idiomatic menggunakan parentheses
import (
"fmt"
"math"
"os"
"strings"
"net/http"
"encoding/json"
)
Go community sangat menyukai gaya import (...) bahkan untuk satu package, karena memudahkan penambahan import di masa depan tanpa mengubah baris yang ada.
Urutan dan Pengelompokan Import #
Konvensi komunitas Go (yang diatur oleh goimports) adalah mengelompokkan import dalam tiga grup yang dipisahkan baris kosong:
import (
// Grup 1: Standard library
"fmt"
"os"
"strings"
// Grup 2: Dependency pihak ketiga
"github.com/gin-gonic/gin"
"go.uber.org/zap"
// Grup 3: Package internal proyek
"myproject/config"
"myproject/handler"
)
Import dengan Alias #
Alias berguna ketika ada konflik nama antara dua package:
import (
"fmt"
mrand "math/rand" // alias: panggil sebagai mrand.Intn()
crand "crypto/rand" // alias: panggil sebagai crand.Read()
)
func main() {
fmt.Println(mrand.Intn(100)) // random number 0-99
}
Alias juga berguna untuk memperpendek nama package yang panjang:
import (
pb "myproject/proto/generated/user/v1"
)
Blank Import #
Kadang kamu perlu mengimpor package hanya untuk efek samping init() function-nya — bukan untuk memanggil fungsinya secara langsung. Gunakan blank identifier _:
import (
"database/sql"
_ "github.com/lib/pq" // import untuk side effect: mendaftarkan PostgreSQL driver
)
Tanpa _, Go akan error karena package pq tidak dipakai secara eksplisit. Dengan _, Go tahu ini disengaja.
Import Titik (Dot Import) — Hindari #
import . "fmt" // semua exported name dari fmt tersedia tanpa prefix
Println("Hello") // bisa langsung, tanpa fmt.Println
Dot import sangat jarang digunakan di kode produksi karena membuat kode sulit dibaca — tidak jelas fungsi Println dari package mana. Satu-satunya penggunaan yang masuk akal adalah di test file untuk DSL tertentu.
Aturan Sintaks yang Tidak Bisa Dilanggar #
Go punya beberapa aturan yang langsung menyebabkan compile error jika dilanggar. Beda dari kebanyakan bahasa di mana hal ini hanya “peringatan” atau style violation, di Go ini adalah hal yang compiler tolak keras. Dan setiap aturan punya alasan yang bagus.
Aturan 1: Import Tidak Dipakai → Compile Error #
package main
import (
"fmt"
"math" // ← compile error: "math" imported and not used
)
func main() {
fmt.Println("hello")
// math tidak pernah dipakai
}
Mengapa? Agar kodebase tidak penuh dengan “dead import” yang terakumulasi seiring waktu dan membuat orang bingung apakah package itu masih relevan. Ini juga mempercepat kompilasi karena Go hanya perlu mengkompilasi package yang benar-benar digunakan.
Aturan 2: Variabel Tidak Dipakai → Compile Error #
func main() {
x := 10
y := 20 // ← compile error: y declared and not used
fmt.Println(x)
}
Mengapa? Alasan yang sama — mencegah dead code. Jika kamu mendeklarasikan variabel, kamu pasti butuh memakainya. Jika tidak, hapus saja. Jika memang sengaja tidak dipakai (misalnya nilai return yang tidak relevan), gunakan blank identifier _.
Aturan 3: Kurung Kurawal Pembuka di Baris yang Sama #
// ✓ BENAR: kurung kurawal di baris yang sama
func main() {
if x > 0 {
fmt.Println("positif")
}
}
// ✗ SALAH: compile error — syntax error: unexpected newline
func main()
{
if x > 0
{
fmt.Println("positif")
}
}
Mengapa? Ini berkaitan dengan mekanisme Automatic Semicolon Insertion — topik yang akan kita bahas sebentar lagi. Singkatnya: Go compiler otomatis menyisipkan semicolon di akhir baris tertentu, dan jika { ada di baris baru setelah func main(), compiler menyisipkan ; sebelum { sehingga menghasilkan syntax error.
Aturan 4: Tidak Ada Tipe Implisit — Harus Eksplisit Jika Diperlukan #
var i int = 10
var f float64 = i // ← compile error: cannot use i (type int) as type float64
// Harus eksplisit:
var f float64 = float64(i) // ✓
Mengapa? Konversi implisit adalah salah satu sumber bug terbesar di C. Go menghilangkannya sepenuhnya — semua konversi tipe harus terlihat jelas di kode.
Automatic Semicolon Insertion #
Go sebenarnya menggunakan semicolon sebagai pemisah statement, tapi kamu tidak perlu menulisnya secara manual. Go lexer otomatis menyisipkan semicolon di akhir baris jika token terakhir di baris tersebut adalah salah satu dari:
- Identifier (nama variabel, fungsi, tipe)
- Integer, float, imaginary, rune, atau string literal
- Keyword:
break,continue,fallthrough,return - Operator:
++,-- - Tanda tutup:
),],}
Ini menjelaskan mengapa kurung kurawal harus di baris yang sama:
// Go melihat ini:
func main() // ← baris diakhiri dengan ), jadi ; disisipkan!
{ // sekarang ini seperti menulis: func main(); {
Dan func main(); { adalah syntax error. Inilah mengapa aturan ini bukan sekadar style — ini aturan teknis yang berhubungan langsung dengan cara Go mem-parse kode.
Exported vs Unexported Identifier #
Go tidak menggunakan keyword public, private, atau protected. Visibilitas ditentukan oleh huruf pertama nama identifier — ini berlaku untuk fungsi, tipe, variabel, konstanta, dan field struct.
Dimulai huruf besar → Exported (dapat diakses dari package lain):
package mathutil
// Exported — bisa dipanggil dari package lain
func Add(a, b int) int {
return a + b
}
type Calculator struct {
// Exported field — bisa diakses dari luar package
Precision int
// Unexported field — hanya bisa diakses dari dalam package mathutil
memory float64
}
// Exported constant
const MaxValue = 1<<31 - 1
// Exported variable
var DefaultTimeout = 30 * time.Second
Dimulai huruf kecil → Unexported (hanya dalam package yang sama):
package mathutil
// Unexported — hanya bisa dipanggil dari dalam package mathutil
func validateInput(a, b int) error {
if a < 0 || b < 0 {
return errors.New("input harus non-negatif")
}
return nil
}
// Unexported type
type internalState struct {
cache map[string]int
mu sync.Mutex
}
// Unexported constant
const maxIterations = 1000
Cara menggunakannya dari package lain:
package main
import "myproject/mathutil"
func main() {
result := mathutil.Add(3, 4) // ✓ exported
fmt.Println(result)
// mathutil.validateInput(3, 4) // ✗ compile error: unexported
// mathutil.internalState{} // ✗ compile error: unexported
}
Ini bukan sekadar access control — ini adalah cara Go mendokumentasikan API publik sebuah package. Apa yang exported adalah apa yang sengaja kamu ekspos. Apa yang unexported adalah implementation detail yang bisa kamu ubah kapan saja tanpa mempengaruhi pengguna package.
Komentar dan Doc Comment #
Go punya dua jenis komentar standar:
// Ini komentar satu baris
/*
Ini komentar multi-baris.
Bisa mencakup banyak baris.
Jarang digunakan di Go modern.
*/
Doc Comment — Dokumentasi Resmi #
Go memiliki sistem dokumentasi built-in bernama godoc. Setiap exported identifier (fungsi, tipe, konstanta, variabel) sebaiknya punya doc comment — komentar tepat di atasnya yang dimulai dengan nama identifier tersebut:
// Package mathutil menyediakan fungsi matematika dasar dan lanjutan.
// Package ini tidak menggunakan library pihak ketiga.
package mathutil
// Add mengembalikan penjumlahan dari dua integer a dan b.
// Tidak ada batasan nilai — bisa negatif atau sangat besar.
func Add(a, b int) int {
return a + b
}
// Calculator menyimpan state kalkulasi dan menyediakan operasi aritmatika.
// Gunakan NewCalculator() untuk membuat instance baru.
type Calculator struct {
// Precision menentukan jumlah digit desimal dalam hasil kalkulasi.
// Default: 2
Precision int
memory float64 // tidak ada doc comment untuk unexported field
}
// NewCalculator membuat Calculator baru dengan presisi default 2.
func NewCalculator() *Calculator {
return &Calculator{Precision: 2}
}
// ErrDivisionByZero adalah error yang dikembalikan ketika pembagi adalah nol.
var ErrDivisionByZero = errors.New("pembagi tidak boleh nol")
Dengan doc comment yang benar, kamu bisa menjalankan:
go doc mathutil.Add
# Output:
# func Add(a, b int) int
# Add mengembalikan penjumlahan dari dua integer a dan b.
# Tidak ada batasan nilai — bisa negatif atau sangat besar.
Zero Value — Inisialisasi Otomatis #
Setiap variabel di Go yang dideklarasikan tanpa nilai eksplisit otomatis mendapat zero value berdasarkan tipenya:
var i int // 0
var f float64 // 0.0
var b bool // false
var s string // "" (string kosong)
var p *int // nil
var sl []int // nil (nil slice)
var m map[string]int // nil (nil map)
var fn func() // nil
Ini bukan nilai random dari memori seperti di C — Go menjamin nilai yang well-defined. Implikasinya:
// Kamu tidak perlu inisialisasi defensif
var counter int // langsung bisa digunakan: counter++
var messages []string // langsung bisa di-append
// Struct juga mendapat zero value di semua field-nya
type Config struct {
Host string
Port int
Debug bool
}
var cfg Config
// cfg.Host = ""
// cfg.Port = 0
// cfg.Debug = false
Zero value memungkinkan kamu menulis kode yang lebih bersih — tidak perlu constructor wajib hanya untuk inisialisasi nilai default.
Gambaran Program yang Lebih Lengkap #
Berikut program yang menunjukkan berbagai elemen sintaks yang akan kamu pelajari di artikel-artikel berikutnya, dengan anotasi di setiap bagian:
// [1] Deklarasi package
package main
// [2] Import dengan pengelompokan
import (
"fmt" // standard library
"math"
"strings"
)
// [3] Package-level constant — exported
const AppVersion = "1.0.0"
// [4] Package-level variable — unexported
var defaultGreeting = "Halo"
// [5] Tipe kustom berbasis tipe dasar
type Celsius float64
type Fahrenheit float64
// [6] Struct dengan exported dan unexported field
type Person struct {
Name string // exported
Email string // exported
age int // unexported — hanya bisa diakses dalam package ini
}
// [7] Method pada struct dengan pointer receiver
func (p *Person) SetAge(a int) {
if a >= 0 && a <= 150 {
p.age = a
}
}
// [8] Method dengan value receiver — hanya membaca
func (p Person) Greet() string {
return fmt.Sprintf("%s, %s! (v%s)", defaultGreeting, p.Name, AppVersion)
}
// [9] Fungsi dengan multiple return values
func celsiusToFahrenheit(c Celsius) (Fahrenheit, error) {
if c < -273.15 {
return 0, fmt.Errorf("suhu %v lebih rendah dari nol absolut", c)
}
return Fahrenheit(c*9/5 + 32), nil
}
// [10] Fungsi main — entry point
func main() {
// [11] Short variable declaration
p := Person{
Name: "Budi",
Email: "[email protected]",
}
p.SetAge(28)
// [12] Memanggil method
fmt.Println(p.Greet())
// [13] Handling multiple return values + error
temp, err := celsiusToFahrenheit(100)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("100°C = %.1f°F\n", temp)
// [14] Package strings — operasi string
words := strings.Fields("Go is awesome")
for i, w := range words {
fmt.Printf("[%d] %s\n", i, strings.ToUpper(w))
}
// [15] Package math
fmt.Printf("π = %.4f\n", math.Pi)
}
Setiap elemen di program ini — tipe kustom, struct, method, multiple return values, error handling, range — punya artikel tersendiri di section ini yang membahasnya secara mendalam.
Ringkasan #
- Tiga komponen wajib program Go yang bisa dieksekusi:
package main,import, danfunc main().- Satu direktori = satu package — semua file
.godalam satu folder harus deklarasikan package yang sama.- Import tidak dipakai = compile error — Go memaksa kode bebas dari dead import.
- Variabel tidak dipakai = compile error — paksa kode tetap bersih dari dead code.
- Kurung kurawal
{harus di baris yang sama — ini teknis, berhubungan dengan automatic semicolon insertion oleh lexer Go.- Exported vs unexported ditentukan huruf pertama:
ExportedFuncbisa diakses dari package lain,internalFunctidak.- Doc comment dimulai dengan nama identifier — dipakai oleh
go docuntuk dokumentasi otomatis.- Zero value menjamin semua variabel terinisialisasi:
0,false,"",nil— tidak ada nilai random dari memori.- Konversi tipe selalu eksplisit — tidak ada silent type coercion seperti di JavaScript atau C.