Mengenal Go
Kenali asal, kekuatan, dan pengguna Go sebelum menulis kode pertama.
Mengenal Go
- 01Pahami konsepnya · 3
- 02Coba contoh kode · 1
- 03Latihan singkat
Kenali asal, kekuatan, dan pengguna Go sebelum menulis kode pertama.
Go (atau Golang) adalah bahasa pemrograman open source yang diciptakan di Google pada 2009 oleh tiga insinyur legendaris: Robert Griesemer, Rob Pike, dan Ken Thompson. Mereka merancang Go untuk menjawab masalah nyata Google: kode yang lambat dikompilasi, bahasa yang terlalu rumit, dan tim yang terus membesar.
Tiga kekuatan utamanya: sintaks yang kecil dan mudah dipelajari (spesifikasinya cuma puluhan halaman), kompilasi sangat cepat yang menghasilkan satu berkas binary mandiri, dan konkurensi bawaan lewat goroutine — jutaan tugas ringan bisa berjalan bersamaan tanpa drama thread berat.
Go dipakai di production oleh nama-nama besar: Docker dan Kubernetes ditulis dengan Go, begitu pula bagian-bagian penting di Dropbox, Uber, Tokopedia, dan PayPal. Ia mendominasi dunia infrastruktur cloud, CLI modern, dan microservice.
Sekadar penampilan pertama: lima baris ini adalah program Go utuh yang bisa langsung dijalankan. Sepanjang kursus ini setiap contoh mengikuti kerangka yang sama — package main, fungsi main, lalu isi program.
Uji: benahi program ini sehingga mencetak dua baris persis: `Halo, Go!` lalu `Siap belajar!`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func main() {
fmt.Println("Halo, Go!")
}Pasang Go di komputermu dan verifikasi pemasangannya.
Buka go.dev/dl lalu unduh installer resmi untuk sistem operasimu — Windows (.msi), macOS (.pkg), atau Linux (.tar.gz). Installer Windows dan macOS cukup dijalankan seperti aplikasi biasa; di Linux, ekstrak berkasnya ke /usr/local dan tambahkan /usr/local/go/bin ke PATH.
Setelah terpasang, buka terminal baru dan jalankan go version untuk memastikan semuanya benar. Perintah go env menampilkan seluruh konfigurasi lingkungan: versi, lokasi GOROOT, dan GOPATH.
Mulai Go 1.20-an ke atas tidak ada lagi langrah wajib lain — mode modul adalah bawaan, jadi kamu bisa langsung menulis kode di folder mana pun tanpa menyiapkan workspace khusus.
go version mencetak versi dan platform terpasang — kalau perintah ini tidak ditemukan, PATH belum berisi folder bin Go. go env GOROOT GOPATH menunjukkan lokasi instalasi (GOROOT) dan folder kerja default (GOPATH).
Kelola proyek dan dependensi dengan go mod.
Module adalah unit proyek di Go: satu folder berisi kode plus berkas go.mod yang mencatat nama modul dan daftar dependensinya. Buat dengan go mod init nama-modul — nama ini biasanya berupa path repository (mis. github.com/kamu/proyek) supaya bisa diimpor dari mana saja.
Menambah pustaka pihak ketiga cukup dengan go get: perintah itu mengunduh versi terpilih dan mencatatnya di go.mod serta go.sum. Sebaliknya, go mod tidy membuang dependensi yang tak lagi dipakai dan menambahkan yang belum tercatat.
Dengan module, folder proyek boleh berada di mana pun — tidak harus di dalam GOPATH. go.mod adalah kontrak versi; go.sum menyimpan checksum agar dependensi yang terunduh selalu identik di setiap komputer.
go mod init membuat berkas go.mod di folder aktif — dari titik ini folder tersebut resmi menjadi satu modul. go get kemudian mengunduh uuid dan menambahkannya ke daftar dependensi; saat berikutnya dibangun di komputer lain, versi yang sama persis yang akan dipakai.
Pustaka eksternal diimpor dengan path lengkapnya persis seperti tercantum di go.mod. Jalankan dengan go run main.go — Go mengambil dependensi dari cache lokal tanpa perlu unduh ulang.
Pahami peran GOPATH di era modern dan warisan sebelumnya.
Sebelum module ada (pra-2018), semua kode Go wajib tinggal di dalam satu folder induk bernama GOPATH — biasanya ~/go — dengan struktur baku bin, pkg, dan src. Setiap proyek hidup di src/github.com/user/proyek dan berbagi satu tumpukan dependensi global.
Mode module menghapus kewajiban itu: kini kode boleh di mana saja dan tiap proyek membawa dependensinya sendiri lewat go.mod. Tapi GOPATH tidak mati — folder itu masih dipakai sebagai cache modul (pkg/mod) dan tempat berkas binary hasil go install (bin).
Jadi singkatnya: untuk menulis kode kamu tidak perlu peduli GOPATH lagi; Go yang mengurusnya. Cukup ketahui lokasinya lewat go env GOPATH kalau suatu saat perlu menghapus cache atau menemukan binary hasil instalasi.
GOMODCACHE menunjuk ke cache modul — semua versi dependensi yang pernah diunduh tersimpan di sini dan dipakai ulang lintas proyek, itulah kenapa go get kedua kali terasa instan. Folder bin di dalam GOPATH berisi hasil go install.
Siapkan editor yang membantu menulis Go dengan nyaman.
Editor paling populer untuk Go adalah Visual Studio Code dengan ekstensi resmi Go. Pasang dari marketplace, buka folder proyek, dan ekstensi menawarkan memasang tooling yang dibutuhkan — terima saja semuanya.
Jantung tooling modern adalah gopls, language server resmi Go. Ia menghidupkan autocomplete, go-to-definition, rename, dan deteksi error langsung saat mengetik. Di belakangnya bekerja gofmt (formatter bawaan — satu gaya kode untuk semua orang Go) dan go vet (pemeriksa kode mencurigakan).
Alternatif lain: GoLand (jetbrains, berbayar tapi lengkap), Neovim dengan plugin LSP, atau Zed. Apa pun pilihannya, pastikan mendukung gopls supaya pengalaman menulis Go setara.
Perintah Go: Install/Update Tools memasang gopls beserta kawan-kawannya (dlv untuk debug, staticcheck, dst). gofmt -w memformat berkas langsung di tempat — selama kamu konsisten memakainya, gaya kodemu identik dengan jutaan programer Go lain.
Kenali perintah go yang dipakai sehari-hari.
Semua alur kerja Go berpusat pada satu binary: perintah go. Yang paling sering dipakai: go run untuk menjalankan cepat saat mengembangkan, go build untuk menghasilkan binary, dan go test untuk menjalankan pengujian.
Perintah pendukung yang sama penting: go fmt memformat kode, go vet memeriksa pola mencurigakan, go mod mengelola dependensi, go get menambah pustaka, go install memasang binary, go doc membaca dokumentasi, dan go help menjelaskan semuanya.
Berbeda dari ekosistem bahasa lain, tidak ada build tool pihak ketiga yang wajib — Makefile pun jarang dibutuhkan. Satu perintah go menutup seluruh siklus: tulis, format, uji, bangun, pasang.
go run mengompilasi ke folder sementara lalu langsung mengeksekusi — pas untuk iterasi cepat. go build meninggalkan berkas executable bernama sesuai modul. Pola ./... berarti "paket ini dan semua subfoldernya", sangat berguna saat proyek membesar.
Go bisa mengompilasi untuk sistem lain dari komputermu sendiri — cukup set GOOS dan GOARCH. Inilah kenapa mendistribusikan program Go ke banyak platform terasa mudah: satu perintah per target, hasilnya binary mandiri tanpa runtime terpisah.
Kenali package main dan fungsi main.
Program Go yang bisa dijalankan selalu berada di package main, dan titik masuknya adalah fungsi main. Keduanya wajib ada tepat satu dalam satu aplikasi. Kode dari paket lain tinggal diimpor lewat pernyataan import.
Paket fmt adalah pustaka standar untuk input/output berformat. fmt.Println menampilkan nilai ke layar lalu berpindah baris, dan menerima seberapa pun argumen yang dipisah koma. Berbeda dari kebiasaan bahasa lain, pernyataan Go tidak perlu diakhiri titik koma — Go menambahkannya sendiri saat kompilasi.
Simpan kode dalam berkas berekstensi .go, lalu jalankan dengan go run main.go di terminal. Untuk menghasilkan berkas tunggal yang bisa dijalankan tanpa Go terpasang, gunakan go build.
Program ini mencetak dua baris: sapaan dan hasil penjumlahan 2 + 3. Println menghitung ekspresi angka sebelum menampilkannya, dan koma antar argumen otomatis diberi spasi. Ubah pesannya lalu jalankan ulang untuk memastikan alur sunting → jalankan terasa cepat.
bufio.Scanner membaca satu baris ketikan dari stdin. Text() mengembalikan teksnya (termasuk spasi betis — makanya dipangkas dengan TrimSpace), lalu Printf mencetak dengan format %s untuk menyisipkan nama. Kombinasi ini adalah cetakan dasar program CLI interaktif.
Uji: program harus mencetak dua baris persis: `Halo!` lalu `100` (hasil 10 × 10).
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func main() {
fmt.Println("Halo dari Go!")
fmt.Println(2 + 3)
}Tulis komentar sebaris, blok, dan komentar dokumentasi.
Go mengenal dua bentuk komentar: // untuk satu baris dan /* ... */ untuk blok. Kompilator mengabaikan semuanya — komentar murni untuk manusia: menjelaskan alasan, bukan mengulang apa yang kode sudah katakan.
Bentuk istimewa adalah komentar dokumentasi: komentar yang ditulis tepat di atas deklarasi (fungsi, tipe, konstanta) dan diawali namanya. Ini adalah sumber dokumentasi resmi Go — perintah go doc dan situs pkg.go.dev membacanya, dan alat dokumentasi lain pun menampilkannya.
Komentar yang baik menjawab "kenapa", bukan "apa". Konvensi komunitas: singkat, kalimat lengkap, di dokumen bahasa Inggris agar konsisten dengan ekosistem — tapi untuk berlatih, bahasa apa pun sah.
Komentar di atas Sapa adalah komentar dokumen: diawali nama fungsinya, menjelaskan apa yang dilakukan. Jalankan go doc Sapa dan kalimat itu muncul. Komentar dalam fungsi menjelaskan langkah spesifik — cukup di mana kodemu butuh alasan tambahan.
Uji: beri komentar pada baris cetaknya agar program berjalan TANPA mencetak apa pun (keluaran kosong).
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
// Sapa menyapa pengguna dengan nama yang diberikan.
func Sapa(nama string) string {
return "Halo, " + nama + "!"
}
func main() {
// Komentar satu baris: penjelas singkat langkah di bawahnya.
fmt.Println(Sapa("Ari")) /* blok: jarang dipakai, */
}Simpan nilai dengan var dan :=, dan kenali tipe dasarnya.
Ada dua cara membuat variabel: var nama string = "Ari" menuliskan tipenya secara eksplisit, sedangkan nama := "Ari" meminta Go menyimpulkan tipenya dari nilai di sebelah kanan. Bentuk pendek := hanya boleh dipakai di dalam fungsi; setelah dibuat, nilainya diperbarui dengan tanda = biasa.
Satu pernyataan bisa membuat beberapa variabel sekaligus: var a, b int atau a, b := 1, 2. Tipe dasar yang paling sering dipakai: string, int (dan kerabatnya int8–int64, uint), float64 untuk desimal, bool, rune untuk satu karakter Unicode, dan byte untuk satu byte. string sebenarnya potongan byte yang tidak bisa diubah isinya — operasi "mengubah" string selalu menghasilkan string baru.
Go ketat soal kebersihan kode: variabel yang dideklarasikan lalu tidak pernah dipakai membuat kompilasi gagal, dan variabel yang belum diisi otomatis berisi nilai nol tipenya — "" untuk string, 0 untuk angka, false untuk bool. Tanda _ menjadi penampung bila suatu hasil memang tidak ingin disimpan.
Tipe umur disimpulkan sebagai int tanpa perlu ditulis. Karena aktif dideklarasikan tanpa nilai awal, ia memegang false — nilai nol untuk bool. Koma pada Println otomatis menyisipkan spasi antar nilainya, sehingga keluaran tetap rapi tanpa perangkai string manual.
Semua nilai di sisi kanan dihitung lebih dulu sebelum ditugaskan, jadi x, y = y, x menukar keduanya tanpa perlu variabel perantara — trik khas penugasan ganda di Go.
new(T) mengalokasikan memori bertipe T berisi nilai nol, lalu mengembalikan pointernya. Kamu jarang membutuhkannya secara langsung — &variabel lebih umum — tetapi memahami new membantu membaca kode yang memakai pointer secara eksplisit.
Uji: isi kota dan tambah umur satu tahun — program harus mencetak persis `Ari 18 Bandung`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func main() {
var nama string = "Ari"
umur := 17
var aktif bool
fmt.Println(nama, umur, aktif)
}Nilai tetap dengan const serta operasi angka dan perbandingan.
const mendeklarasikan nilai yang dikunci saat kompilasi — sekali ditetapkan, tidak bisa diubah. Ia cocok untuk tarif, batas, atau label tetap. Beberapa konstanta sejenis bisa dikelompokkan dalam satu blok const agar rapi.
Operator aritmatika standar tersedia: +, -, *, /, dan % untuk sisa pembagian. Penting diingat, pembagian dua bilangan bulat menghasilkan bilangan bulat juga — sisanya dibuang — sehingga 7 / 2 bernilai 3. Bila butuh hasil desimal, salah satunya perlu menjadi float64. Operator perbandingan (== != < > <= >=) selalu menghasilkan bool, dan operator logika && (dan), || (atau), ! (tidak) menggabungkan beberapa perbandingan. Penyingkat seperti += dan ++ tersedia untuk memperbarui nilai di tempat.
Trik khas Go bernama iota: di dalam blok const, iota adalah penghitung yang bertambah satu pada setiap baris. Ia menggantikan enumerasi manual — cara idiomatis mendefinisikan daftar nilai berurutan seperti hari, level, atau kode status.
Tarif per kilometer dibuat const karena tidak berubah selama program hidup. Ekspresi tarifOjol * km menghitung total, lalu total < 20000 menghasilkan bool yang disimpan di murah. Ubah nilai km dan perhatikan bagaimana murah ikut berubah — begitu total melewati batas, nilainya berganti false.
iota dimulai dari nol dan bertambah satu per baris dalam blok const — ekspresi iota + 1 di baris pertama diulang otomatis di baris berikutnya, jadi Selasa sampai Jumat tidak perlu ditulis ulang. Hasilnya daftar konstanta berurutan yang rapi dan bebas salah tulis.
Uji: hitung harga setelah diskon 10% — program harus mencetak persis `225000`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
const tarifOjol = 2500
func main() {
km := 6
total := tarifOjol * km
murah := total < 20000
fmt.Println("Total:", total, "Murah?", murah)
}Ambil keputusan dengan if/else dan switch.
Blok if menjalankan kodenya hanya bila kondisinya true. Rantai else if menyediakan cabang tambahan dan else menampung sisanya. Berbeda dari banyak bahasa, kondisi tidak dibungkus tanda kurung, tetapi kurung kurawal wajib — bahkan bila isinya hanya satu pernyataan.
if bisa diawali pernyataan singkat yang variabelnya hanya hidup di dalam blok itu: if sisa := n % 2; sisa == 0 { ... }. Pola ini sering dipakai untuk menyiapkan nilai penentu tepat sebelum keputusan diambil, tanpa menodai cakupan luar.
switch hadir dalam dua rupa. Switch bernilai membandingkan satu variabel terhadap banyak case; switch tanpa nilai (seperti contoh) memeriksa kondisi berurutan seperti if/else if yang dirapikan. Setiap case berhenti sendiri tanpa break — tapi bila kamu sengaja ingin jatuh ke case berikutnya, tulis fallthrough.
switch tanpa nilai memeriksa tiap case dari atas ke bawah; untuk 82, case nilai >= 80 adalah yang pertama terpenuhi sehingga cetakannya B dan sisanya dilewati. default berjalan bila tidak ada case yang cocok. Coba gabungkan dua kondisi dalam satu case dengan koma: case nilai >= 90, nilai == 100.
Satu case bisa memuat beberapa nilai sekaligus lewat koma. fallthrough memaksa eksekusi lanjut ke case berikutnya tanpa memeriksa kondisinya — dipakai jarang, tapi penting kamu tahu ada: "sabtu" mencetak Weekend! lalu lanjut mencetak Santai.
Uji: tulis if/else-if/else dengan cabang >= 60 → `C+`. Dengan nilai 82 harus mencetak `B`; ganti ke 60 dan harus mencetak `C+`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func main() {
nilai := 82
switch {
case nilai >= 90:
fmt.Println("A")
case nilai >= 80:
fmt.Println("B")
default:
fmt.Println("C")
}
}Ulangi langkah dengan for dan for range.
for adalah satu-satunya kata kunci perulangan di Go. Bentuk lengkapnya punya tiga bagian — for i := 0; i < 5; i++ — yaitu nilai awal, syarat lanjut, dan langkah setiap putaran; ketiganya dipisah titik koma dalam satu baris.
Tulis syaratnya saja (for n < 10) dan ia bekerja seperti while di bahasa lain; tanpa syarat sama sekali (for {}) perulangan berjalan tanpa henti sampai bertemu break atau return. Di dalam putaran, break keluar dari perulangan dan continue melewatkan sisa putaran untuk langsung melompat ke putaran berikutnya.
for range adalah bentuk yang paling sering dipakai untuk menyusuri koleksi: slice dan array memberi indeks serta elemen, map memberi kunci serta nilai, dan string memberi indeks serta rune satu karakter. Perulangan bersarang bisa diberi label agar break dan continue menunjuk loop luar secara eksplisit.
range mengembalikan dua nilai sekaligus yang di sini ditampung i dan nama. Bila indeks tidak diperlukan, ganti namanya dengan _ — menyisakan nama tak terpakai membuat Go menolak kompilasi. Keluarannya berpasangan: 0 apel, 1 mangga, 2 jeruk.
Label luar: ditempel di depan loop terluar, lalu break luar keluar dari KEDUA loop sekaligus — tanpa label, break hanya keluar dari loop terdalam dan angka 5 masih ikut tercetak. continue luar juga sah: melewati sisa iterasi luar.
Uji: lengkapi loop agar mencetak `7 x 1 = 7` sampai `7 x 10 = 70` — sepuluh baris persis.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func main() {
buah := []string{"apel", "mangga", "jeruk"}
for i, nama := range buah {
fmt.Println(i, nama)
}
}Kumpulan tetap dengan ukuran yang melekat pada tipenya.
Array menyimpan sejumlah nilai bertipe sama dengan ukuran tetap: [3]int berarti tepat tiga bilangan bulat, dan angka 3 itu menjadi bagian dari tipenya — [3]int dan [4]int adalah dua tipe berbeda yang tidak bisa saling diisi.
Deklarasinya bisa menyebut ukuran lalu mengisi per indeks, mengisi langsung ([3]int{80, 90, 75}), atau memakai [...] agar Go menghitung sendiri jumlahnya. Akses elemen lewat indeks mulai dari nol, dan len mengembalikan jumlah elemennya.
Sifat penting yang membedakan dari slice: array adalah nilai utuh. Mengirim array ke fungsi atau menugaskannya ke variabel lain menyalin SELURUH isinya — perubahan di salinan tidak menyentuh aslinya. Karena kaku, array jarang dipakai langsung; ia menjadi fondasi di balik slice yang jauh lebih populer.
nilai dideklarasikan dulu lalu diisi per indeks; singkat memakai [...] sehingga ukurannya dihitung dari jumlah nilai (6). len melaporkan jumlah elemen tiap array. Keluaran: 3 6 90 42.
Fungsi ubah menerima salinan array, jadi modifikasi di dalamnya lenyap saat fungsi pulang — asli tetap [1 2 3]. Bandingkan dengan slice pada pelajaran berikutnya yang justru membagi memori; kontras inilah yang membuat keduanya mudah diingat.
Array dari array membentuk matriks: [2][3]int berarti dua baris bertiga. Aksesnya berantai papan[baris][kolom]. Pola yang sama berlaku untuk slice multidimensi.
Uji: hitung rata-rata lima nilai — program harus mencetak persis `75`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func main() {
var nilai [3]int
nilai[0] = 80
nilai[1] = 90
nilai[2] = 75
singkat := [...]int{4, 8, 15, 16, 23, 42}
fmt.Println(len(nilai), len(singkat), nilai[1], singkat[5])
}Daftar fleksibel yang menjadi koleksi utama di Go.
Slice adalah daftar fleksibel bertipe sama dan koleksi yang paling sering kamu pakai di Go. Deklarasinya tanpa angka: []int atau []string dengan literal, atau lewat make([]int, panjang) bila mulai kosong.
Slice sejatinya jendela menuju array di belakangnya — ia menyimpan penunjuk, panjang (len), dan kapasitas (cap). Mengambil nilai[1:3] membuat slice baru yang menunjuk memori yang sama, sehingga mengubah elemen lewat satu slice terlihat dari slice lain yang berbagi array itu. cap mengukur ruang sisa sebelum slice harus pindah ke memori baru.
append menambah elemen di akhir dan mengembalikan slice hasil — wajib ditampung kembali. Saat kapasitas penuh, Go menyiapkan array baru lebih besar; saat masih longgar, elemen baru menempati ruang kosong yang mungkin masih dibagi slice lain. copy(dst, src) menyalin min(len(dst), len(src)) elemen. Bentuk tiga indeks nilai[0:2:2] sekaligus memotong kapasitasnya, dan paket slices (Go 1.21+) menambah utilitas modern: Sort, Contains, Index, Reverse.
append(nilai, 100) membuat slice berisi empat angka. potongan := nilai[1:3] mengambil elemen indeks 1 dan 2 (90 dan 75) — panjangnya 2, tetapi cap-nya masih mengikuti array asal sampai akhir. Coba potongan[0] = 999 lalu cetak nilai: elemen indeks 1 ikut berubah, bukti memori dibagi.
copy hanya memindahkan sejumlah elemen yang muat di tujuan. Jebakan kedua lebih halus: a masih punya kapasitas sisa, jadi append(a, 99) menulis LANGSUNG ke ruang kosong array asal dan menimpa elemen indeks 2 milik asal. Saat slice berbagi array, append bisa menimpa data tetangga.
Sejak Go 1.21, paket slices membungkus operasi umum dengan aman dan generik: Sort mengurutkan, Contains memeriksa keberadaan, Index mencari posisi. Utilitas lama di paket sort kini pelan-pelan digantikan ini.
Uji: ambil tiga nama pertama, ubah elemen pertamanya menjadi `zeta`, cetak nama; lalu urutkan potongan itu dan cetak. Keluaran harus `[zeta budi citra andi erik]` lalu `[budi citra zeta]`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"slices"
)
func main() {
nilai := []int{80, 90, 75}
nilai = append(nilai, 100)
potongan := nilai[1:3]
fmt.Println(len(nilai), cap(nilai), potongan)
}Pasangan kunci–nilai untuk data yang dicari berdasarkan nama.
Map menyimpan pasangan kunci–nilai dan cocok untuk data yang dicari berdasarkan nama, bukan posisi: stok barang, umur teman, atau pengaturan aplikasi. Buat dengan literal map[string]int{...} atau dengan make(map[string]int) bila mulai kosong. Kunci harus tipe yang bisa dibandingkan (comparable) — string, angka, bool, atau gabungannya.
Mengakses kunci yang tidak ada tidak membuat program gagal — ia mengembalikan nilai nol tipenya. Karena "nol" bisa berarti nilai asli nol atau kunci memang tidak ada, Go menyediakan bentuk dua nilai: v, ok := m[k], di mana ok bernilai true hanya bila kuncinya benar-benar tersedia.
delete(m, k) menghapus entri dan len(m) menghitung pasangan yang tersimpan. Saat disusuri dengan for range, urutan keluarannya TIDAK dijamin — bila butuh urutan pasti, ambil kunci-kuncinya, urutkan, lalu iterasi sesuai urutan itu. Nilai map juga bisa berupa tipe majemuk seperti slice atau map lain.
Baris stok["susu"] = 5 menambahkan kunci baru pada map. jumlah, ada := stok["teh"] mengambil nilai 8 sekaligus konfirmasi true. Bila "teh" dihapus dengan delete, akses yang sama mengembalikan 0 dan false — itulah alasan pola ok layak dijadikan kebiasaan.
Iterasi langsung lewat range menghasilkan urutan acak — Go sengaja mengacaknya. Untuk keluaran stabil: kumpulkan kunci ke slice, urutkan, lalu akses nilainya satu per satu menurut urutan itu.
Uji: kurangi stok kopi (2) dan HAPUS kunci yang habis — program harus mencetak persis `map[susu:5 teh:0]`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func main() {
stok := map[string]int{"kopi": 12, "teh": 8}
stok["susu"] = 5
jumlah, ada := stok["teh"]
fmt.Println(jumlah, ada)
}Parameter, nilai balik ganda, dan fungsi variadic.
Fungsi mengemas langkah yang bisa dipanggil ulang. Parameter dan tipenya ditulis di dalam tanda kurung, sedangkan tipe nilai balik ditulis setelahnya: func jumlah(a, b int) int. Fungsi tanpa nilai balik tidak perlu menuliskan tipe balik sama sekali.
Go dapat mengembalikan beberapa nilai sekaligus — func bagi(a, b int) (int, error) — dan pola inilah yang dipakai hampir seluruh pustaka standar untuk melaporkan kegagalan. Nilai balik pun bisa diberi nama; variabelnya sudah tersedia di dalam fungsi dan return polos cukup untuk mengembalikan semuanya.
Parameter variadic (angka ...int) menerima seberapa pun argumen saat dipanggil, lalu berubah menjadi slice biasa di dalam fungsi. Ia wajib menempati posisi terakhir, dan slice yang sudah ada bisa dikirim utuh dengan akhiran ....
Pemanggilan jumlah(2, 4, 6) mengemas tiga angka menjadi slice []int{2, 4, 6} di dalam fungsi. range menyusurinya sementara _ membuang indeks yang tidak dipakai; total dirangkum dan dikembalikan sebagai hasil akhir. Panggil dengan angka berbeda, atau kirim slice utuh dengan jumlah(data...), untuk merasakan kedua bentuknya.
Karena nilai balik diberi nama (min dan max), keduanya sudah tersedia sebagai variabel lokal dan return polos mengembalikannya sekaligus. Pemanggil menampung keduanya dalam dua variabel — pola yang sama dipakai fungsi standar seperti os.Getpid.
Uji: lengkapi statistik agar memanggilannya dengan (7, 3, 9, 5) mencetak persis `9 6` (maksimum, rata-rata).
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func jumlah(angka ...int) int {
total := 0
for _, n := range angka {
total += n
}
return total
}
func main() {
fmt.Println(jumlah(2, 4, 6))
}Fungsi yang mengingat lingkungannya dan fungsi sebagai argumen.
Di Go, fungsi adalah nilai: bisa disimpan ke variabel, dimasukkan ke slice atau map, dikirim sebagai parameter, dan dikembalikan dari fungsi lain. Tipe untuk nilai fungsi ditulis seperti tanda tangan — misalnya func(int) int.
Closure terjadi saat fungsi yang dikembalikan "menangkap" variabel dari lingkungan pembuatnya — variabel itu tetap hidup dan diingat di antara pemanggilan. Inilah cara Go membuat penghitung, akumulator, atau pembangkit nilai tanpa class.
Menerima fungsi sebagai parameter membuat kode jadi fleksibel: satu fungsi filter yang sama bisa menyaring dengan kriteria apa pun yang dikirim pemanggil. Sort pun bekerja begitu — slices.SortFunc menerima fungsi pembanding buatanmu.
Fungsi anonim yang dikembalikan menutup (close over) variabel hitung — meski buatPenghitung sudah selesai, hitung tetap hidup milik si next. Setiap pemanggilan next() menambah dan mengembalikan nilai yang sama persisten. Buat penghitung kedua dan ia punya hitung sendiri yang terpisah.
saring tidak tahu kriterianya — ia hanya tahu bentuknya: func(int) bool. Pemanggil mengirim fungsi anonim berbeda untuk hasil berbeda dari kode yang sama. Pola ini adalah inti gaya fungsional di Go dan dipakai luas oleh pustaka standar.
Uji: buat saring(data, kriteria) lalu ambil yang habis dibagi 3 — program harus mencetak `[3 9 6]`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func buatPenghitung() func() int {
hitung := 0
return func() int {
hitung++
return hitung
}
}
func main() {
next := buatPenghitung()
fmt.Println(next(), next(), next()) // 1 2 3
}Alamat memori variabel dan mengubah nilai dari fungsi lain.
Pointer menyimpan alamat memori variabel lain. Operator & mengambil alamat sebuah variabel (&skor), tanda * pada tipe (*int) menyatakan "pointer ke int", dan tanda * pada variabel pointer (*poin) membaca atau menulis nilai yang tersimpan di alamat itu.
Secara bawaan Go menyalin nilai saat argumen dikirim ke fungsi — perubahan di dalam fungsi tidak menyentuh variabel aslinya. Dengan menerima pointer, fungsi bekerja langsung pada variabel sungguhan milik pemanggil, sehingga perubahannya menetap. Sifat ini juga membuat struct besar lebih hemat dikirim sebagai pointer daripada disalin utuh.
Go sengaja tidak menyediakan aritmatika pointer seperti C — pointer di sini aman: tidak bisa dijumlah atau digeser, hanya menunjuk atau nil. Operator & dan * menjadi fondasi method dengan pointer receiver serta penerusan struct antar fungsi secara efisien.
naikkan(&skor) mengirim alamat skor, lalu *poin += 10 menambah 10 pada nilai yang tersimpan di alamat tersebut — hasilnya 80. Hilangkan & dan * lalu jalankan ulang: cetakannya tetap 70 karena fungsi hanya mengubah salinannya. Kontras kecil ini adalah inti terpenting dari pointer.
Field lewat pointer struct diakses langsung dengan tanda titik — Go otomatis melakukan (*t).X. Kemudahan kecil ini membuat kode pointer di Go terasa hampir seperti kode biasa.
Uji: lengkapi tukar lewat pointer — program harus mencetak persis `2 1`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func naikkan(poin *int) {
*poin += 10
}
func main() {
skor := 70
naikkan(&skor)
fmt.Println(skor)
}Cetakan data yang mengelompokkan nilai bertipe berbeda.
Struct mengelompokkan beberapa nilai bertipe berbeda di bawah satu nama — cetakan data untuk entitas nyata seperti siswa, produk, atau pesanan. Definisikan dengan type Nama struct, buat nilainya dengan Nama{field: nilai}, dan akses isinya dengan tanda titik.
Bentuk-bentuk pendukung: struct anonim untuk data sekali pakai, struct bersarang (field berupa struct lain), dan embedded struct — menuliskan tipe tanpa nama field menjadikannya "menyatu", sehingga field serta method-nya bisa diakses langsung dari luar. Inilah cara Go menggantikan pewarisan (inheritance): komposisi, bukan hirarki.
Struct adalah nilai: penugasan dan pengiriman ke fungsi menyalin isinya. Karena itu struct besar biasa dilewatkan sebagai pointer. Saat dicetak dengan %v, seluruh field tampil; format %+v menambahkan nama fieldnya.
Struct bersarang: field Tinggal berupa struct Alamat, diakses berantai ari.Tinggal.Kota. %+v mencetak semua field beserta namanya — sangat berguna untuk melihat isi struct saat debugging.
Karena Pengguna ditanam tanpa nama field, isinya "menyatu" dengan Admin — a.Email bekerja seolah field itu milik Admin sendiri. Method pada tipe yang ditanam juga ikut terwariskan; inilah komposisi ala Go.
Struct anonim dideklarasikan dan diisi dalam satu langkah — pas untuk data sementara yang tidak akan dipakai ulang, misalnya bentuk respons sekali cetak. Untuk data yang muncul berkali-kali, selalu definisikan tipe bernama.
Uji: tanam Pemasok ke dalam Produk lalu cetak — program harus mencetak persis `Kopi dari PT Jaya`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
type Alamat struct {
Kota string
KodePos string
}
type Siswa struct {
Nama string
Poin int
Tinggal Alamat
}
func main() {
ari := Siswa{Nama: "Ari", Poin: 10, Tinggal: Alamat{Kota: "Bandung", KodePos: "40115"}}
fmt.Printf("%+v\n", ari)
}Fungsi yang menempel pada tipe.
Method adalah fungsi yang menempel pada sebuah tipe. Receiver ditulis di antara func dan nama method: func (s *Siswa) Tambah(p int). Receiver seperti parameter pertama yang tidak tertulis — di dalam method, isinya bisa diakses lewat namanya (s).
Receiver nilai (s Siswa) bekerja pada salinan: method bisa membaca semua field tetapi perubahannya tidak menetap. Receiver pointer (s *Siswa) bekerja pada aslinya: field bisa diubah dan struct besar tidak perlu disalin. Aturan praktisnya: mulai dari pointer receiver bila method mengubah data atau structnya besar; konsisten satu gaya dalam satu tipe.
Method membuat kode terbaca seperti kalimat: ari.Tambah(25) atau waktu.Add(jam). String bawaan Go tidak memiliki method ToUpper; gunakan fungsi strings.ToUpper(teks). Ekspresi seperti v.Logika disebut method value: potongan method yang terikat pada nilai tertentu dan bisa diperlakukan sebagai fungsi biasa.
Tambah memakai receiver pointer karena mengubah field — kenaikan Poin tersimpan pada ari sendiri. Label memakai receiver nilai karena hanya membaca; hasilnya string baru. Go otomatis mengubah ari.Tambah(25) menjadi (&ari).Tambah(25), jadi pemanggilannya tetap mulus.
Method tidak hanya untuk struct — tipe apa pun yang kamu definisikan (berbasis float64, string, slice) bisa punya method. Celcius menjadi tipe bernama dengan perilakunya sendiri, membuat kode domain (suhu, mata uang, jarak) terbaca jelas.
Uji: tambahkan method Kurang dan Lulus (poin >= 70) — program harus mencetak persis `true`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
type Siswa struct {
Nama string
Poin int
}
func (s *Siswa) Tambah(p int) {
s.Poin += p
}
func (s Siswa) Label() string {
return fmt.Sprintf("%s (%d poin)", s.Nama, s.Poin)
}
func main() {
ari := Siswa{Nama: "Ari", Poin: 10}
ari.Tambah(25)
fmt.Println(ari.Label())
}Huruf besar-kecil menentukan siapa yang boleh mengakses.
Go tidak punya kata kunci public atau private. Penggantinya sederhana: identifier yang berawalan huruf KAPITAL diekspor (public) — bisa diakses dari paket mana pun; huruf kecil tidak diekspor (private) — hanya hidup di dalam paketnya.
Aturan ini berlaku seragam untuk semuanya: field struct, method, fungsi, tipe, konstanta, sampai variabel paket. fmt.Println diekspor; fungsi internal fmt seperti pencetaknya tidak. Saat mengimpor math, kamu boleh memakai math.Pi tapi bukan math internals-nya.
Konsekuensinya nyata saat merancang paket sendiri: nama yang diekspor adalah API publikmu — ubah hurufnya dan kode pemakai putus. Kebiasaan baik: ekspor sesedikit mungkin, perluas belakangan. Sebuah struct dengan field privat dijadikan aman lewat method pembaca/penulis (getter/setter) — di Go konvensinya getter tanpa awalan Get: s.Nama().
Dari luar paket hitung, hanya Luas yang terlihat — pi tersembunyi meski fungsinya ikut terpakai di dalam. Coba panggil hitung.pi() dari main: kompilator menolak karena tidak diekspor. Pemisahan publik/privat se-celah ini membuat API paket tetap ramping.
Uji: panggil Luas(7) dari main — program harus mencetak persis `153.93791`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
// ---- berkas: hitung/hitung.go ----
package hitung
// Luas diekspor: publik untuk siapa saja.
func Luas(r float64) float64 {
return pi() * r * r
}
// pi tidak diekspor: rahasia paket ini.
func pi() float64 {
return 3.14159
}
// ---- berkas: main.go ----
package main
import (
"fmt"
"hitung"
)
func main() {
fmt.Println(hitung.Luas(7)) // 153.93791
}Kontrak perilaku yang dipenuhi tipe apa pun yang punya methodnya.
Interface adalah daftar method yang menjadi kontrak perilaku. Berbeda dari banyak bahasa, Go tidak meminta pernyataan "mengimplementasikan" — tipe mana pun yang memiliki method yang dimaksud otomatis dianggap memenuhi interface, secara implisit dan tanpa upacara.
Fungsi yang menerima parameter bertipe interface bisa dipakai bersama semua tipe pemenuh kontrak itu. Ini membalik cara berpikir: kode tidak perlu tahu tipe concrete-nya, cukup apa yang bisa dilakukannya — dan tipe baru cukup menyediakan method yang tepat untuk langsung masuk.
Dari nilai interface, tipe aslinya bisa ditarik kembali lewat type assertion (v.(*Email)) dengan bentuk aman v, ok := x.(T), atau ditelusuri bertingkat lewat type switch. Interface juga bisa saling ditanam (embed) untuk menggabung kontrak — io.ReadWriter gabungan Reader dan Writer.
Email tidak pernah menyatakan hubungannya dengan Pengirim — keberadaan method Kirim yang bertanda sama sudah cukup. Berkat itu, kirimSemua menerima Email hari ini dan tipe lain besok (misalnya SMS) tanpa perubahan sedikit pun pada fungsinya; inilah yang membuat interface terasa seperti colokan universal.
Type switch memeriksa tipe dinamis nilai interface cabang per cabang — variabel v di tiap cabang sudah bertipe tepat, tanpa perlu assertion lagi. Bentuk satu kali: n, ok := x.(int) mengembalikan nilai dan keberhasilan; ok false berarti tipenya bukan int.
Uji: buat struct SMS dengan method Kirim lalu kirim lewat []Pengirim — harus dua baris persis: `Email: Halo!` lalu `SMS: Halo!`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
type Pengirim interface {
Kirim(pesan string) string
}
type Email struct{}
func (e Email) Kirim(pesan string) string {
return "Email: " + pesan
}
func kirimSemua(p Pengirim, pesan string) {
fmt.Println(p.Kirim(pesan))
}
func main() {
kirimSemua(Email{}, "Halo!")
}Tipe yang ditempati semua nilai — dan kapan memakainya.
Interface tanpa method sama sekali disebut interface kosong: interface{}. Sejak Go 1.18, nama pendeknya adalah any. Karena kontraknya kosong, semua tipe memenuhinya. Contoh memakai interface{} agar dapat dijalankan dengan compiler Go 1.13 di situs ini.
fmt.Println dapat menerima nilai dengan tipe berbeda dalam satu panggilan. Pada Go modern parameternya ditulis ...any; pada Go lama bentuk setaranya ...interface{}. Untuk koleksi campuran juga ada []interface{} atau []any pada Go modern.
Sebelum menggunakan nilainya sebagai tipe konkret, periksa dengan type assertion atau type switch. Jika tipe bisa dipastikan sejak awal, gunakan tipe konkret atau generics pada Go 1.18+.
Satu panggilan tampilSemua menampung lima nilai bertipe berbeda karena parameter ...any. Di dalam loop, tiap v bertipe any — bisa dicetak, tetapi untuk dihitung atau dibandingkan perlu dikembalikan ke tipe aslinya dulu lewat assertion.
Bentuk dua nilai data.(int) mengembalikan isi plus keberhasilan — ok false tidak membuat program jatuh, hanya berarti tipenya bukan int. Pola ini adalah satu-satunya jalan aman membongkar isi any.
Uji: jumlahkan hanya elemen bertipe int lewat type assertion — program harus mencetak persis `59`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func tampilSemua(nilai ...interface{}) {
for _, v := range nilai {
fmt.Print(v, " ")
}
fmt.Println()
}
func main() {
tampilSemua("umur", 17, 55.5, true, []int{1, 2})
}Memeriksa tipe dan nilai saat program berjalan.
Refleksi adalah kemampuan program memeriksa dirinya sendiri: bertanya "ini tipenya apa, berapa field-nya, nilainya berapa" saat berjalan. Go menyediakannya lewat paket reflect, dan kamu sudah merasakan hasilnya tanpa sadar — fmt mencetak struct apa pun, encoding/json mengubah struct menjadi JSON, dan flag membaca nama field: semuanya refleksi di balik layar.
Dua pintu masuk utama: reflect.TypeOf(x) memberi tahu tipe dinamis sebuah nilai, dan reflect.ValueOf(x) membungkus nilainya agar bisa diperiksa — .Kind() melaporkan jenis dasarnya (int, string, struct), .NumField() dan .Field(i) menyusuri field struct, .Interface() mengembalikan nilai aslinya.
Refleksi kuat tapi mahal dan rapuh: melewati pemeriksaan kompilator, lambat dibanding kode biasa, dan error-nya baru muncul saat runtime. Prinsip umum komunitas Go: "refleksi tidak pernah diperlukan, hanya kadang memudahkan" — pakai untuk pustaka serba-guna, hindari di logika aplikasi sehari-hari. Kebutuhan serupa kini sering lebih baik dijawat generics.
Kind() melaporkan jenis dasar nilai (int), bukan nama tipe pembuatnya. Untuk struct, TypeOf lalu NumField/Field menyusuri skema fieldnya — nama dan tipenya tercetak tanpa kamu menuliskannya. Inti kerja json.Marshal bekerja persis begini.
Uji: cetak nama dan tipe tiap field lewat reflect — harus tiga baris persis: `Nama string`, `Poin int`, `Kelas string`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"reflect"
)
type Siswa struct {
Nama string
Poin int
}
func main() {
x := 42
fmt.Println(reflect.TypeOf(x).Kind()) // int
s := Siswa{Nama: "Ari", Poin: 10}
t := reflect.TypeOf(s)
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
fmt.Printf("%s %s\n", f.Name, f.Type)
}
}Jalankan fungsi sebagai tugas ringan yang berjalan bersamaan.
Tambahkan kata go sebelum pemanggilan fungsi dan ia berjalan sebagai goroutine — tugas ringan yang dijadwalkan runtime Go untuk berjalan bersamaan. Memulai goroutine semurah memanggil fungsi biasa; ribuan bahkan jutaan dalam satu program masih wajar. Runtime menaruhnya di sejumlah kecil thread OS dan mengatur pergantianya secara otomatis.
Goroutine membuat pemanggilan TIDAK menunggu: baris berikutnya langsung jalan meski tugasnya belum selesai. Di contoh pertama, time.Sleep dipakai hanya demi demo — menahan main supaya program tidak berhenti sebelum goroutine mencetak. Solusi yang benar menunggu lewat channel (pelajaran berikutnya) atau WaitGroup.
Fungsi main sendiri adalah goroutine utama; saat ia selesai, seluruh program berhenti seketika — goroutine lain yang masih berjalan ikut dimatikan. Kesadaran akan ini adalah kunci mengapa pola sinkronisasi sangat penting di Go.
Kedua pemanggilan go tugas berjalan hampir bersamaan — "mulai A" dan "mulai B" tercetak berdampingan sebelum keduanya "selesai". Tanpa time.Sleep di akhir main, program akan langsung berhenti dan output goroutine mungkin tidak pernah muncul. Sleep di sini hanya alat peraga; pelajaran channel dan WaitGroup menggantinya dengan sinkronisasi sungguhan.
Lima goroutine diluncurkan dalam sekejap dan mencetak dalam urutan yang tidak pasti. Perhatikan i dikirim sebagai parameter n — menangkap i langsung dari closure akan terkena jebakan variabel loop yang berubah; menyalinnya lewat parameter membuat tiap goroutine memegang nilainya sendiri.
Uji: tiga goroutine (jeda 100/300/500 ms) masing-masing mencetak `selesai` + namanya SETELAH tidurnya — harus tercetak `selesai A`, `selesai B`, `selesai C` berurutan.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"time"
)
func tugas(nama string) {
fmt.Println("mulai", nama)
time.Sleep(500 * time.Millisecond)
fmt.Println("selesai", nama)
}
func main() {
go tugas("A") // tidak menunggu
go tugas("B")
time.Sleep(1 * time.Second) // demo saja: beri waktu goroutine
fmt.Println("main selesai")
}Pipa bertipe untuk mengirim dan menerima antar goroutine.
Tugas bersamaan tidak boleh berbagi variabel seenaknya — hasilnya balapan data yang tak terduga. Channel mengatasinya: make(chan string) membuat pipa bertipe, dan operator <- mengirim di satu sisi serta menerima di sisi lain.
Channel tanpa buffer bersifat sinkron: pengiriman menunggu sampai ada penerima, dan penerimaan menunggu sampai ada pengirim. Kedua sisi berjabat tangan — kamu tahu persis saat data berpindah. Ini sekaligus menjadi titik tunggu alami: main yang menerima <-ch otomatis menunggu goroutine selesai mengirim.
Fungsi main adalah goroutine; bila ia selesai lebih dulu seluruh program berhenti. Menerima dari channel menahan main tepat selama dibutuhkan — inilah mengapa contoh klasik goroutine + channel tidak memerlukan Sleep sama sekali.
go pesan(...) memulai tugas baru, dan penerimaan <-ch memblokir main sampai nilai tiba — tidak perlu Sleep, tidak ada balapan. Setelah diterima, program berakhir dengan seluruh goroutine diam. Hilangkan baris penerimaan lalu jalankan beberapa kali: kadang pesan muncul, kadang tidak.
Channel dua arah seperti percakapan: main mengirim pekerjaan, pekerja menerima, mengolah, lalu mengirim balik hasil. Setiap panah <- adalah titik jeda yang menjaga kedua goroutine selangkah — pola ini adalah cetakan semua arsitektur worker di Go.
Uji: dua goroutine mengirim kuadrat 3 dan 9 ke satu channel; terima keduanya dan cetak jumlahnya — harus persis `90`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func pesan(nama string, ch chan string) {
ch <- "Halo dari " + nama
}
func main() {
ch := make(chan string)
go pesan("goroutine", ch)
fmt.Println(<-ch)
}Channel dengan ruang tunggu agar pengiriman tidak langsung menunggu.
make(chan int, 3) membuat channel ber-buffer: ia bisa menampung tiga nilai sebelum pengiriman mulai menunggu. Selama ruang masih ada, ch <- v langsung selesai tanpa perlu penerima yang siap — seperti kotak pesan yang bisa diisi beberapa surat dulu.
Begitu buffer penuh, pengiriman berikutnya memblokir sampai ada yang menerima. Sebaliknya penerimaan pada buffer kosong juga menunggu. len(ch) melaporkan berapa nilai sedang menunggu dan cap(ch) ukuran buffernya.
Buffer mengubah watak sinkron channel menjadi semi-asinkron: kirim dan terima tidak wajib berpasangan saat itu juga. Manfaatnya melicinkan perbedaan kecepatan antar goroutine (producer cepat, consumer lambat), tapi jangan dijadikan alat menunda masalah — buffer terlalu besar hanya menyembunyikan kemacetan, bukan menyelesaikannya. Aturan praktis: mulai dari tanpa buffer, tambah buffer saat polanya jelas butuh.
Tiga pengiriman berturut-turut berhasil tanpa satu pun penerima karena buffer berukuran 3. Penerimaan mengambil sesuai urutan masuk (FIFO) — 1 dulu, baru 2. Setelah ketiga nilai diterima, buffer kosong dan pengiriman keempat akan menunggu.
Baris ketiga yang dikomentari akan menghentikan program dengan fatal error: all goroutines are asleep — pengiriman ke buffer penuh menunggu selamanya karena tak ada penerima. Ukuran buffer adalah jumlah penyangga, bukan kapasitas tak terbatas.
Uji: isi buffer-5 dengan 1..5 lewat loop TANPA penerima, lalu kosongkan — harus mencetak `1 2 3 4 5` (satu angka per baris).
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 1 // tidak menunggu: buffer masih kosong
ch <- 2
ch <- 3
fmt.Println(len(ch), cap(ch)) // 3 3
fmt.Println(<-ch) // 1 (yang pertama masuk)
fmt.Println(<-ch) // 2
}Tunggu beberapa channel sekaligus dan tanggapilah yang pertama siap.
select seperti switch untuk channel: ia menunggu SEMUA case-nya dan menjalankan satu yang channel-nya pertama siap — baik pengiriman maupun penerimaan. Bila beberapa siap bersamaan, Go memilih secara acak agar adil.
Case dengan default bersifat non-blok: bila tak ada channel yang siap, cabang default langsung jalan. Ini berguna untuk polling — mencoba mengambil data bila ada, lanjut bila tidak — atau mengirim bila consumer tersedia.
select adalah bahan dasar pola penting: timeout (pelajaran berikutnya), membatalkan pekerjaan, dan multiplexing — satu goroutine melayani banyak sumber. Tanpa select, menunggu dua channel berarti dua penerimaan berurutan yang saling mengunci.
Loop dua kali karena ada dua pesan yang datang. Pada putaran pertama select, channel cepat siap lebih dulu sehingga cabangnya jalan; putaran kedua menunggu lambat. Tanpa select, menerima cepat dulu baru lambat tetap bisa — tetapi select-lah yang memungkinkan bereaksi terhadap sumber mana pun yang lebih dulu siap.
Channel kosong tanpa penerima-pengirim lain membuat select langsung melompat ke default — program tidak pernah terkunci. Pola ini dipakai untuk loop event: periksa data, proses bila ada, lakukan hal lain bila tidak.
Uji: terima dua sumber (1 detik & 3 detik) memakai select — harus mencetak `satu detik` lalu `tiga detik`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"time"
)
func main() {
cepat := make(chan string)
lambat := make(chan string)
go func() {
time.Sleep(1 * time.Second)
cepat <- "satu detik"
}()
go func() {
time.Sleep(3 * time.Second)
lambat <- "tiga detik"
}()
for i := 0; i < 2; i++ {
select {
case pesan := <-cepat:
fmt.Println("dari cepat:", pesan)
case pesan := <-lambat:
fmt.Println("dari lambat:", pesan)
}
}
}Tutup channel dan susuri isinya sampai habis.
close(ch) menutup sebuah channel: tidak ada pengiriman lagi yang diterima (mengirim ke channel tertutup membuat program panic), tetapi penerimaan tetap boleh dan menghabiskan sisa nilai di buffer.
Setelah semua nilai terkuras, penerimaan berikutnya langsung mengembalikan nilai nol. Bentuk v, ok := <-ch membedakan keadaan: ok true berarti nilai asli, ok false berarti channel sudah tertutup dan kosong.
Yang paling sering dipakai: for v := range ch — loop menerima terus sampai channel ditutup, lalu selesai dengan rapi. Konvensinya penting: channel ditutup oleh PENGIRIM, tidak pernah penerima, dan menutup memang tujuannya memberi tahu "tidak ada data lagi". Menerima dari channel nil juga mengembalikan nilai nol tanpa menunggu — kadang berguna sebagai channel mati.
range menerima 1, 2, 3 lalu berhenti dengan sendirinya begitu close terdeteksi — tidak perlu tahu berapa banyak data yang akan datang. Coba kirim setelah close: ch <- 4 membuat panic. Pola kirim-N-lalu-close ini adalah bentuk paling umum channel di program nyata.
Penerimaan pertama masih mendapat nilai 10 dari sisa buffer dengan ok true. Penerimaan kedua mendapat 0 dan ok false — sinyal resmi bahwa channel tertutup dan kosong. Bentuk dua nilai ini adalah cara eksplisit memeriksa bila range tidak memungkinkan.
Uji: goroutine mengirim lima fibonacci pertama lalu menutup channel; main menerimanya dengan for range — harus `1 1 2 3 5` (satu per baris).
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func kirim(ch chan int) {
for i := 1; i <= 3; i++ {
ch <- i
}
close(ch) // pengirim memberi tahu: data habis
}
func main() {
ch := make(chan int)
go kirim(ch)
for v := range ch {
fmt.Println(v) // 1, 2, 3, lalu loop berhenti sendiri
}
}Batasi lama menunggu dengan time.After dan select.
Menunggu tanpa batas itu berbahaya: goroutine lawan bisa macet, jaringan bisa menggantung, dan programmu ikut beku. Go menjawabnya dengan kombinasi select dan time.After(durasi) — channel yang mengirim satu nilai tepat setelah durasi tersebut berlalu.
Diletakkan sebagai salah satu case select, time.After menjadi alarm: bila data tiba lebih dulu, cabang data yang jalan dan alarm diabaikan; bila batas waktu tercapai duluan, cabang timeout jalan dan kamu bebas memutuskan — kembalikan error, coba ulang, atau tinggalkan.
Timeout adalah pola wajib untuk panggilan jaringan dan pekerjaan yang tidak boleh menggantung. Prinsipnya: setiap channel yang kamu tunggu layak punya rencana B — berapa lama kamu sanggup menunggu, dan apa yang dilakukan setelahnya.
Layanan simulasi butuh tiga detik, tetapi alarm dipasang satu detik — cabang timeout menang dan program keluar setelah satu detik, tidak ikut menggantung tiga detik. Ubah durasi time.After menjadi 5 detik dan cabang data yang akan menang.
Membungkus select dalam fungsi bernilai (string, error) membuat timeout menjadi bagian normal alur error — pemanggil menanganinya dengan if err != nil seperti kegagalan lain. Inilah bentuk timeout dalam produksi.
Uji: layanan lambat 2 detik versus alarm 500ms — program harus mencetak persis `timeout: terlalu lama` (lalu berhenti, tidak menunggu 2 detik).
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"errors"
"fmt"
"time"
)
func ambilLambat(ch chan string) {
time.Sleep(3 * time.Second) // simulasi layanan lambat
ch <- "data"
}
func main() {
ch := make(chan string)
go ambilLambat(ch)
select {
case data := <-ch:
fmt.Println("dapat:", data)
case <-time.After(1 * time.Second):
fmt.Println("timeout: terlalu lama menunggu")
}
}Jadwalkan pembersihan dan hentikan program dengan sengaja.
defer menjadwalkan sebuah pemanggilan untuk dijalankan tepat sebelum fungsi pulang — apa pun jalur pulangnya: return biasa, return di tengah cabang, bahkan panic. Pendaftarannya LIFO: yang didaftar terakhir dijalankan lebih dulu, seperti tumpukan piring.
Penggunaan paling khas adalah pembersihan berpasangan: buka berkas → defer tutup, kunci mutex → defer buka kunci, buka koneksi → defer putuskan. Menuliskannya berdampingan dengan pembukaannya memastikan tidak ada yang lupa, berapa pun cabang return yang berkembang di tengah fungsi.
os.Exit(kode) menghentikan program seketika dengan kode keluar (0 sukses, selainnya gagal). Penting: os.Exit TIDAK menjalankan defer yang tertunda — hanya gunakan di titik akhir yang memang final, seperti setelah flag.Parse gagal atau saat perintah CLI selesai total.
Isi fungsi jalan dulu ("pertama"), lalu defer dieksekusi berkebalikan urutan pendaftaran: yang didaftar belakangan ("kedua") lebih dulu daripada yang didaftar awal ("ketiga"). Sifat LIFO ini menjaga pembersihan bersarang terurai dengan benar — kunci yang terakhir dipasang dibuka pertama.
defer f.Close() ditulis tepat setelah os.Open sukses — dari titik itu tidak ada jalur pulang yang bisa lupa menutup berkas, berapa pun return yang ditambahkan belakangan. Tanpa defer, tiap cabang error harus menutup manual; satu saja terlewat, berkas bocor sampai program mati.
Program berhenti tepat setelah "sebelum exit" — baris defer tidak pernah tereksekusi karena os.Exit memotong seluruh alur pulang. Karena itu os.Exit hanya untuk titik final; untuk alur normal, biarkan main pulang sendiri agar defer bekerja.
Uji: daftarkan tiga defer yang mencetak `ketiga`, `kedua`, `pertama` — keluaran harus empat baris: `isi fungsi`, `ketiga`, `kedua`, `pertama`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func main() {
defer fmt.Println("ketiga (defer terakhir didaftar)")
defer fmt.Println("kedua (LIFO: defer ini lebih dulu)")
fmt.Println("pertama (isi fungsi biasa)")
}Pola error Go, plus panic dan recover untuk keadaan darurat.
Go menangani kegagalan yang diharapkan lewat nilai biasa, bukan pengecualian. Fungsi yang bisa gagal mengembalikan satu nilai tambahan bertipe error; nil berarti sukses, selain itu berarti masalah. Konvensinya, nilai error menempati posisi terakhir dan diperiksa dengan if err != nil sesegera mungkin.
Buat error baru dengan errors.New("pesan") atau fmt.Errorf dengan verb %w untuk membungkus error lama dengan konteks tambahan — bungkutan %w bisa dibuka lagi dengan errors.Is dan errors.As untuk memeriksa akar masalahnya. Pemanggil bebas memilih: berhenti dengan return atau log.Fatal, mencoba ulang, atau meneruskan masalah ke atas.
Panic adalah jalur darurat yang berbeda: untuk keadaan yang tak seharusnya terjadi (indeks nyasar, invariant pecah). Panic merambat naik dan menghentikan program — kecuali ditangkap oleh recover yang dipasang lewat defer di fungsi yang sama. Pasangan defer + recover mengubah panic menjadi error biasa; ini yang dilakukan framework HTTP agar satu handler rusak tidak menjatuhkan server.
bagi(10, 2) berhasil sehingga err bernilai nil dan blok pengecekan dilewati. Panggil dengan pembagi nol dan jalur gagal aktif: pesan errors.New tercetak lewat fmt.Println. Perhatikan return setelahnya — sekadar mencetak tidak cukup; eksekusi selanjutnya harus dihentikan agar hasil yang tidak sahih tidak ikut terpakai.
fmt.Errorf dengan %w membungkus error asli di balik konteks baru tanpa memusnahkannya — errors.Is masih bisa mencocokkan akarnya. Hasil cetakan err tetap informatif ("pesan \"kaos\": stok habis") sementara program bisa bereaksi terhadap penyebab spesifik.
recover hanya sah di dalam fungsi yang di-defer. Saat panic merambat, defer di aman berjalan, recover menangkap nilainya, dan alur kembali normal — panic diubah menjadi error yang bisa dikembalikan. Program tetap hidup; tanpa ini ia mati dengan pesan goroutine panic.
Uji: panggil bagi(7, 0) dan tangani errornya — program harus mencetak persis `Gagal: pembagi tidak boleh nol`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"errors"
"fmt"
)
func bagi(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("pembagi tidak boleh nol")
}
return a / b, nil
}
func main() {
hasil, err := bagi(10, 2)
if err != nil {
fmt.Println("Gagal:", err)
return
}
fmt.Println("Hasil:", hasil)
}Cetak nilai dengan verb format Printf.
fmt.Printf memakai verb — placeholder yang diawali % — untuk menyisipkan nilai ke dalam teks. Verb terpenting: %v (representasi umum apa pun), %+v (struct dengan nama field), %#v (sintaks Go), %s (string), %d (bilangan bulat), %f (desimal, mis. %.2f dua angka di belakang koma), %t (bool), %T (tipe nilai), dan %% untuk karakter persen itu sendiri.
Keluarga sejawat: Sprintf mengembalikan string alih-alih mencetak — sumber pesan error dan teks berformat; Fprintf menulis ke io.Writer seperti berkas; Errorf membangun error dengan format.
Verb %T dan %v adalah sahabat debugging: satu menunjukkan tipe dinamis, satu isinya. Untuk nilai any atau interface, keduanya sering menjadi cara tercebat memahami apa yang sebenarnya kamu pegang.
%.2f membulatkan tampilan ke dua desimal (3.81) tanpa mengubah nilainya. %v mencetak struct polos {3 7} sedangkan %+v menyertakan nama field — sangat membantu saat inspeksi data. %T melaporkan float64 dan %% menjadi tanda persen literal.
Sprintf membangun string berformat untuk disimpan, dikirim, atau dijadikan pesan error — pola yang akan kamu pakai di mana-mana, dari log sampai template respons API.
Uji: satu Printf dengan verb yang tepat — harus mencetak persis `Ari berumur 17, tinggi 168.50 cm, aktif: true (100%)`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
type Titik struct{ X, Y int }
func main() {
t := Titik{X: 3, Y: 7}
nama := "Ari"
ipk := 3.8125
fmt.Printf("hai %s, IPK %.2f\n", nama, ipk)
fmt.Printf("struct: %v / %+v\n", t, t)
fmt.Printf("tipe ipk: %T, persen: 100%%\n", ipk)
}Bilangan acak untuk permainan dan sampel data.
Paket math/rand menghasilkan bilangan acak semu (pseudorandom): cukup acak untuk permainan, simulasi, dan sampel — tetapi BUKAN untuk keamanan. Sejak Go 1.20 sumber acaknya otomatis di-seed, jadi rand.Intn langsung bisa dipakai tanpa rangkaian seed manual yang dulu wajib.
Fungsi inti: rand.Intn(n) memberi angka 0 sampai n-1, rand.Int() seluruh int, rand.Float64() desimal 0–1, dan rand.Shuffle mengacak urutan slice di tempat. Memilih elemen acak dari koleksi biasanya sebatas indeks acak: data[rand.Intn(len(data))].
Untuk kebutuhan kriptografi — token, password, kunci sesi — pakailah crypto/rand: sumbernya dari sistem operasi dan aman dipakai menghadapi penyerang. math/rand yang bisa diprediksi akan berbahaya di sana; membedakan keduanya adalah keputusan desain, bukan detail.
rand.Intn(6) menghasilkan 0–5, jadi +1 menggesernya menjadi lemparan dadu 1–6. Elemen acak diambil lewat indeks acak dengan len sebagai batas. Editor situs memakai Go 1.13, sehingga rand.Seed diperlukan agar urutan berbeda setiap eksekusi; mulai Go 1.20, sumber acak global otomatis di-seed.
rand.Shuffle menerima panjang slice dan fungsi penukar; ia memanggil fungsi itu berkali-kali dengan pasangan indeks acak. Hasilnya pengacakan di tempat tanpa slice baru — standar untuk mengocok kartu, urutan soal kuis, atau sampel acak.
Uji: lempar dua dadu sepuluh kali — program harus mencetak sepuluh baris `lempar N: total X` lalu satu baris akhir `total 7 muncul K kali`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
// Dibutuhkan oleh Go 1.13 pada editor situs.
rand.Seed(time.Now().UnixNano())
fmt.Println("dadu:", rand.Intn(6)+1)
fmt.Println("desimal:", rand.Float64())
warna := []string{"merah", "hijau", "biru"}
fmt.Println("pilih:", warna[rand.Intn(len(warna))])
}Baca, format, dan ubah representasi waktu.
time.Now() memberi waktu saat ini; time.Date(...) membangun waktu spesifik. Nilai time.Time membawa tanggal, jam, zona, dan lokasi sekaligus — perbandingan (Before/After/Equal) serta selisih (.Sub) bekerja benar lintas zona.
Menyusun tampilannya unik di Go: alih-alih kode seperti YYYY-MM-DD, layout ditulis dengan waktu referensi Mon Jan 2 15:04:05 MST 2006 — angka-angkanya sengaja 1 (bulan), 2 (tanggal), 3 (jam), 4 (menit), 5 (detik), 2006 (tahun). t.Format("2006-01-02 15:04") menghasilkan bentuk yang kamu tulis polanya; konstanta siap pakai seperti time.RFC3339 juga tersedia.
Parsing adalah arah sebaliknya: time.Parse(layout, teks) membaca teks waktu menjadi time.Time memakai layout yang sama. Kegagalan format mengembalikan error — selalu periksa. Kombinasi Format + Parse menutup hampir semua kebutuhan: dari API, berkas log, sampai input pengguna.
Layout "Senin, 2 Januari 2006 15:04" dipenuhi otomatis oleh nilai waktu — Go mengenali tiap elemen referensinya (Senin=Monday, 2=tanggal, Januari=month-name, 15 jam, 04 menit) dan menggantinya dengan nilai nyata. time.RFC3339 menghasilkan format standar internet "2006-01-02T15:04:05Z07:00".
Layout yang sama yang dipakai Format dipakai Parse — "2006-01-02 15:04" mengajari parser di mana tiap bagian berada. Setelah jadi time.Time, seluruh method tersedia: ambil komponen, bandingkan, atau cetak ulang dalam format lain (02/01/2006).
Uji: parse tanggal `2007-01-15` dengan layout `2006-01-02` lalu cetak tahunnya — harus persis `2007`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"time"
)
func main() {
sekarang := time.Now()
fmt.Println(sekarang.Format("Senin, 2 Januari 2006 15:04"))
fmt.Println(sekarang.Format(time.RFC3339))
}Jadwalkan sekali atau berulang dengan waktu.
time.NewTimer(durasi) membuat timer yang mengirim satu nilai waktu ke channel .C setelah durasi berlalu — sekali saja. Menerimanya lewat <-t.C menahan program tepat sampai alarm berbunyi; time.Sleep(d) adalah versi singkatnya bila tidak butuh channel.
time.NewTicker(durasi) adalah alarm berulang: channel .C-nya menerima nilai setiap durasi — detak jantung untuk polling berkala, animasi, pengambilan data rutin. Ticker mampu dihentikan lewat .Stop() bila tidak lagi dipakai (dan channel-nya harus dikosongkan bila masih ada penerima). Timer pun bisa dibatalkan sebelum berbunyi dengan .Stop().
Kombinasi ticker + select + timeout adalah cetakan tugas terjadwal modern: satu loop yang setiap detak memeriksa pekerjaan, sambil siap dihentikan lewat channel sinyal atau batas waktu. Untuk penjadwalan kompleks lintas proses, ada pustaka cron — tapi dalam satu proses, ticker sudah sangat jauh.
Timer mengirim waktu kecualinya ke channel C tepat satu detik kemudian; penerimaan <-timer.C menjadi titik tunggu yang rapi. Bedanya dengan Sleep: channel C bisa dijadikan case select — digabung dengan sinyal lain atau timeout — sesuatu yang Sleep tidak bisa.
Loop ini berdetak tiap 300 ms sementara time.After memasang batas akhir 1,2 detik — select menyeimbangkan keduanya. Saat batas tiba, ticker.Stop() memberhentikan detak dan loop keluar: pola penjadwal yang bisa dihentikan dengan bersih.
Uji: ticker 500 ms — program harus mencetak `detak 1` sampai `detak 5` lalu `selesai` (enam baris, lalu berhenti).
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"time"
)
func main() {
timer := time.NewTimer(1 * time.Second)
fmt.Println("menunggu alarm...")
<-timer.C
fmt.Println("alarm berbunyi!")
}Ukur dan hitung selisih waktu secara type-safe.
time.Duration adalah tipe khusus untuk rentang waktu — di balik layar int64 nanodetik, tetapi kamu hampir selalu menulisnya dengan konstanta yang terbaca manusiawi: 5*time.Second, 250*time.Millisecond, 2*time.Hour. Perkalian seperti itu adalah idiom, bukan kebetulan sintaks.
Operasi aritmatikanya lengkap: durasi bisa dijumlah, dikurang, dikalikan skalar, dibanding, dan dikonversi ke satuan lain lewat .Seconds(), .Milliseconds(), .Minutes(). Selisih dua waktu didapat lewat a.Sub(b) yang menghasilkan Duration; tanda negatif berarti a lebih awal.
Dua pemakaian utama: mengukur lama eksekusi (t0 := time.Now(); ...; time.Since(t0)) dan membatasi tunggu (timeout, TTL cache). time.Since(t0) adalah kependekan dari time.Now().Sub(t0) — dan hasilnya siap dicetak dengan %v yang otomatis memilih satuan terbaca ("1.5s", "2m30s").
time.Since(mulai) mengukur rentang eksekusi — cetakan %v otomatis menampilkan "750.123ms" dalam satuan yang enak dibaca. Konstanta Duration bisa dijumlah langsung (2 jam + 30 menit) lalu dikonversi ke menit lewat .Minutes().
Duration dibandingkan dengan operator biasa (> < ==) karena tipenya teratur. Fungsi pembungkus seperti ini mengubah pengukuran waktu menjadi keputusan program: melewati ambang 500 ms berarti "lambat" — pintu masuk sederhana menuju monitoring dan circuit breaker.
Uji: ukur pengurutan 10.000 angka — bila durasinya di bawah 1 detik program harus mencetak persis `cepat`, selain itu `lambat`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"time"
)
func main() {
mulai := time.Now()
time.Sleep(750 * time.Millisecond)
fmt.Printf("lama: %v\n", time.Since(mulai))
tugas := 2*time.Hour + 30*time.Minute
fmt.Println(tugas, "=", tugas.Minutes(), "menit")
}Ubah angka, string, dan byte dengan benar.
Konversi antar tipe numerik ditulis seperti pemanggilan fungsi: float64(n), int(f), string(r). Go tidak pernah mengonversi diam-diam — perbedaan tipe selalu eksplisit. Perlu diingat: int(f) memotong desimal (3.9 menjadi 3), dan mengecilkan rentang (int64→int8) memotong bit tinggi — hasilnya bisa mengejutkan bila nilai tak muat.
Antara string dan angka, Go sengaja tidak menyediakan cast langsung: string(65) menghasilkan "A" (rune!), bukan "65". Jembatannya adalah paket strconv: strconv.Itoa(65) → "65", strconv.Atoi("65") → (65, error), ParseFloat, ParseBool, FormatFloat, FormatInt dengan basis.
string dan []byte saling konversi langsung karena string memang potongan byte; []rune mengubah ke potongan karakter Unicode — penting untuk teks yang memuat emoji atau aksara non-Latin sebelum diindeks per karakter.
Konversi numerik memotong (3.9→3). strconv menjadi jembatan angka↔teks yang benar: Itoa dan Atoi (yang mengembalikan error untuk teks tak valid). string(rune(65)) menegaskan jebakan: kode 65 menjadi huruf "A" — bukan teks "65" seperti dugaan banyak orang.
len(string) menghitung BYTE: é dan ☕ masing-masing memakan 2-3 byte sehingga len menghasilkan 10 padahal karakternya 7. []rune memecah per karakter Unicode — hitungan yang cocok dengan intuisi manusia. Kesadaran byte-vs-rune menyelamatkanmu dari bug indeks pada teks internasional.
Uji: ubah teks `42` menjadi int, kalikan dua, lalu kembalikan sebagai string — program harus mencetak persis `84`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"strconv"
)
func main() {
f := 3.9
fmt.Println(int(f)) // 3 — dipotong, bukan dibulatkan
n := 65
fmt.Println(strconv.Itoa(n)) // "65"
m, err := strconv.Atoi("42")
fmt.Println(m, err)
fmt.Println(string(rune(65))) // "A" — konversi rune, bukan angka
}Olah teks dengan paket strings.
Paket strings menyediakan peralatan lengkap untuk teks. Pemeriksaan: Contains, HasPrefix, HasSuffix, EqualFold (perbandingan tak pedulikan kapital). Pemotongan: Split (per pemisah), Fields (per spasi, pembersih ganda), TrimSpace, Trim/TrimPrefix. Penggabungan dan pengubahan: Join, Replace (dengan n untuk membatasi), ToUpper, ToLower, Repeat.
Pencarian posisi: Index mengembalikan posisi pertama atau -1, Count menghitung kemunculan, Cut membelah sekali menjadi before/after/Found — pola baru yang ramping untuk parsing pas seperti "key=value".
Untuk merangkai banyak potongan secara efisien ada strings.Builder: menambah lewat .WriteString lalu .String() di akhir. Perulangan s += potongan membuat salinan berulang (kompleksitas kuadratik); Builder menyimpannya dalam buffer yang tumbuh — standar untuk membangun teks panjang.
TrimSpace memangkas spasi dua sisi; Fields memecah per spasi (berapapun jumlahnya) langsung menjadi []string bersih — pasangan pembuka hampir setiap olah teks. ReplaceAll mengganti semua kemunculan; versi Replace(s, lama, baru, n) membatasi n kali bila perlu.
Cut membelah sekali pada pemisah pertama dan melaporkan keberhasilannya tanpa error — lebih bersih daripada kombinasi Index + slicing untuk pasangan key=value. Builder menerima fmt.Fprintf langsung (ia adalah io.Writer) lalu menyerahkan hasil utuhnya lewat .String().
Uji: hitung kata dan ganti `seru` menjadi `mantap` — harus dua baris persis: `7` lalu `Go itu mantap dan Go itu cepat`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"strings"
)
func main() {
teks := " Go itu seru dan Go itu cepat "
bersih := strings.TrimSpace(teks)
kata := strings.Fields(bersih)
fmt.Println(kata, len(kata))
fmt.Println(strings.ReplaceAll(bersih, "Go", "Golang"))
fmt.Println(strings.ToUpper(bersih))
}Cocokkan dan ekstrak pola teks dengan regexp.
Reguler expression mendeskripsikan pola teks: \d+ untuk deretan angka, [a-z]+ untuk huruf kecil, ^...$ untuk jangkar awal-akhir, dan tanda kurung (...) untuk menangkap bagian. Go memakai sintaks RE2 — cepat dan aman dari eksplosi waktu pencarian, dengan biaya kecil: tidak ada backreference.
Pola dikompilasi dulu lewat regexp.MustCompile(`...`) (panic saat pola salah — untuk konstanta saat program dimuat) atau regexp.Compile yang mengembalikan error (untuk pola dari input). Kompilasi sekali, pakai berkali-kali.
Operasi utama pada objek pola: MatchString memeriksa kecocokan, FindString mengembalikan kecocokan pertama, FindAllString semua kecocokan dengan batas -1, FindStringSubmatch mengembalikan grup penangkap (indeks 0 seluruh kecocokan, 1..n tiap kurung), dan ReplaceAllString mengganti dengan rujukan $1 ke grup.
Pola \d+ berarti "satu atau lebih angka". MatchString menjawab ada-tidaknya; FindAllString mengumpulkan setiap kecocokan — pada teks itu ia menemukan "08" dan "17". Argumen -1 berarti tanpa batas jumlah hasil.
Tiap kurung menjadi grup penangkap: m[0] seluruh kecocokan (andi@kampus.com), m[1] bagian pertama (andi), m[2] kedua (kampus). FindAllStringSubmatch menjalankannya untuk semua kemunculan — cetakan dasar ekstraksi data dari teks semi-terstruktur.
Uji: perbaiki pola agar menemukan angka UTUH — program harus mencetak persis `[12 03 2007]`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"regexp"
)
func main() {
pola := regexp.MustCompile(`\d+`)
fmt.Println(pola.MatchString("pesanan #40123")) // true
fmt.Println(pola.FindAllString("jam 08 dan 17 menit", -1))
}Ubah data biner menjadi teks aman-URL dan kembali.
Base64 merepresentasikan data biner dengan 64 karakter teks (A–Z, a–z, 0–9, + dan /) sehingga bisa lewat media yang hanya menerima teks: JSON, header HTTP, data-URL gambar, atau konten email. Setiap 3 byte menjadi 4 karakter — ukuran hasil selalu sekitar 4/3 dari asal.
Di Go, keduanya tinggal satu baris: base64.StdEncoding.EncodeToString(data) dan base64.StdEncoding.DecodeString(teks). Paketnya bekerja pada []byte; string cukup dikonversi lewat []byte(s).
Varian StdEncoding memakai + dan / yang bermasalah di URL. Untuk data yang akan menempati alamat web, gunakan URLEncoding (- dan _) atau RawURLEncoding tanpa padding =. Memilih varian yang salah biasanya berujuk pada hasil decode yang gagal diam-diam — selalu cocokkan encoder dan decodernya.
EncodeToString dan DecodeString saling melengkapi — data biner masuk, teks aman keluar, dan sebaliknya tanpa kehilangan satu byte pun. Tanda = di akhir adalah padding agar panjang hasil kelipatan empat. DecodeString mengembalikan error untuk teks yang bukan base64 valid — periksa bila sumbernya tidak dipercaya.
Byte biner ini menghasilkan + dan / pada StdEncoding — karakter yang berbahaya di query string karena berarti spasi dan pemisah path. URLEncoding menggantinya dengan - dan _ sehingga hasil aman ditanam langsung dalam URL.
Uji: encode data biner berikut — program harus mencetak persis `+/++AQ==`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"encoding/base64"
"fmt"
)
func main() {
asli := []byte("rahasia kecil")
kode := base64.StdEncoding.EncodeToString(asli)
fmt.Println(kode) // cmFodGFzaWEga2VjaWw=
balik, _ := base64.StdEncoding.DecodeString(kode)
fmt.Println(string(balik)) // rahasia kecil
}Sidik jari data untuk integritas dan pembandingan.
Fungsi hash mengubah data ukuran apa pun menjadi sidik jari tetap — SHA-1 selalu 20 byte. Input sama memberi hasil sama, sementara perubahan kecil pada input biasanya mengubah banyak digit hash. Karena itu hash berguna untuk mendeteksi perubahan dan menamai cache.
Di Go: buat hasher dengan sha1.New(), isi lewat Write (bisa bertahap untuk data besar), lalu Sum(nil) menyerahkan hasil []byte. Untuk string pendek, semua bisa sebaris. Hasil biner biasa ditampilkan heksadesimal lewat fmt.Sprintf("%x", h) atau hex.EncodeToString.
Catatan keamanan penting: SHA-1 sudah terbukti rentan terhadap tumbukan — jangan gunakan untuk tanda tangan, sertifikat, atau menyimpan password. Untuk integritas yang membutuhkan ketahanan kriptografis gunakan SHA-256; untuk password gunakan algoritme khusus seperti bcrypt atau Argon2. SHA-1 masih bisa dipakai untuk checksum non-keamanan seperti penamaan cache.
sha1.Sum adalah pintasan untuk input yang muat sekaligus — hasilnya array [20]byte. %x merendernya heksadesimal. Perhatikan efek longsoran: "golang" dan "Golang" hanya beda satu huruf kecil-besar, tetapi sidik jarinya berubah total — itulah yang membuat hash peka mendeteksi perubahan.
Antarmuka sha256 identik dengan sha1 — New, Write, Sum — hanya hasilnya 32 byte dan algoritmanya belum tertumbukkan. Pola New/Write bertahap juga memungkinkan meng-hash berkas besar per potongan tanpa memuat semuanya ke memori.
Uji: bandingkan sidik jari `golang` dan `Golang` — bila keduanya berbeda program harus mencetak persis `beda`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"crypto/sha1"
"fmt"
)
func sidikJari(teks string) string {
h := sha1.Sum([]byte(teks))
return fmt.Sprintf("%x", h)
}
func main() {
fmt.Println(sidikJari("golang"))
fmt.Println(sidikJari("Golang")) // satu huruf beda = hasil total beda
}Baca argumen baris perintah dan flag bernama.
Cara paling mentah: os.Args adalah slice string berisi seluruh argumen — indeks 0 nama program, sisanya apa pun yang diketik. Untuk CLI kecil itu sudah cukup, tapi memparsing opsi --nama nilai sendirian rentan salah.
Paket flag memberikan parsing yang rapi: deklarasikan tiap opsi dengan flag.String("nama", "default", "deskripsi"), flag.Int, flag.Bool; panggil flag.Parse(); lalu akses nilainya lewat pointer yang dikembalikan. Flag Boolean ditulis --verbose saja; flag bernilai --port 8080 atau --port=8080. Sisa argumen non-flag tersedia di flag.Args().
flag otomatis menyediakan --help yang mencetak seluruh opsi beserta deskripsinya — dokumentasi CLI gratis. Konvensinya penulisan: flag dideklarasikan di package-level atau dalam func init, Parse dipanggil di awal main.
Deklarasi flag mengembalikan POINTER ke variabel nilainya — dibaca lewat *nama setelah Parse. Jalankan: go run main.go --nama=Andi --ulang 3 mencetak sapaan tiga kali; tanpa flag, default "tamu" dan sekali. Coba --help untuk melihat daftar opsi otomatis.
os.Args tidak memilah flag sama sekali — semuanya string mentah termasuk nama berkas program di depan. Cocok untuk "berikan saya satu kata" sederhana; begitu opsi muncul, pindah ke flag agar parsing dan bantuan terkelola.
Uji: tanpa argumen, flag jatuh ke default (`c`→`f`, angka cadangan 100) — program harus mencetak persis `c -> f: 212`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"flag"
"fmt"
)
func main() {
nama := flag.String("nama", "tamu", "nama yang disapa")
ulang := flag.Int("ulang", 1, "berapa kali menyapa")
flag.Parse()
for i := 0; i < *ulang; i++ {
fmt.Println("Halo,", *nama)
}
}Jalankan program lain dari kode Go.
os/exec menjalankan perintah eksternal seolah kamu mengetiknya di terminal: exec.Command("git", "status") menyiapkan proses baru. Panggil .Output() untuk menjalankan sambil menangkap stdout — hasilnya []byte plus error; .CombinedOutput() menggabungkan stdout dan stderr; .Run() sekadar menjalankan tanpa peduli keluaran.
Argumen perintah dikirim terpisah — bukan satu string utuh — sehingga tanpa shell, tanpa risiko injeksi. Bila kamu benar-benar butuh fitur shell (pipe, wildcard), bungkus dengan exec.Command("sh", "-c", "ls *.go | wc -l") dengan input yang sudah tervalidasi.
Exit code non-nol dilaporkan sebagai error bertipe *exec.ExitError — cek lewat errors.As untuk membedakan "program gagal" dari "program tidak ada". Untuk interaksi dua arah (kirim input, baca keluaran mengalir), Command menyediakan StdinPipe dan StdoutPipe.
Command("go", "version") mencari binary go di PATH lalu menjalankannya dengan satu argumen. Output() menunggu proses selesai dan menyerahkan stdout-nya — di sini versi Go tercetak persis seperti di terminal. Bila perintah tidak ditemukan, err berisi kesalahan exec.
Ada dua rasa kegagalan: program berjalan tapi gagal (ExitError, memuat kode keluar dan stderr) versus program sama sekali tidak ditemukan (error biasa). errors.As memisahkannya — di luar folder git, contoh ini mencetak kode keluar 128.
Uji: jalankan perintah yang pasti tidak ada dan tangkap errornya dengan errors.As — program harus mencetak persis `perintah tidak ditemukan`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"os/exec"
)
func main() {
keluar, err := exec.Command("go", "version").Output()
if err != nil {
fmt.Println("gagal:", err)
return
}
fmt.Print(string(keluar))
}Baca, tulis, dan susuri isi berkas.
Cara termudah membaca berkas kecil: os.ReadFile(nama) mengembalikan seluruh isi sebagai []byte; pasangannya os.WriteFile(nama, data, 0644) menimpa atau membuat berkas dengan izin Unix. Keduanya menutup berkas otomatis — tidak ada yang di-defer.
Untuk berkas besar atau berbaris, buka dengan os.Open lalu susuri dengan bufio.Scanner: Scan() maju satu baris, Text() menyerahkan isinya. Pola baku: f, err := os.Open(...); defer f.Close(); scanner := bufio.NewScanner(f); for scanner.Scan(). Menulis bertahap: os.Create + WriteString + defer Close.
Direktori juga beroperasi lewat paket os: MkdirAll membuat folder bertingkat, RemoveAll menghapus, dan os.ReadDir mendaftar isi folder sebagai entri ber-name dan IsDir. Untuk berjalan menyeluruh pohon folder besar, filepath.WalkDir mengunjungi tiap berkas dengan callback.
WriteFile membuat atau menimpa catatan.txt dengan izin 0644 (pemilik baca-tulis, lainnya baca), lalu ReadFile menyerahkan seluruh isinya dalam satu panggilan. Keduanya menutup berkas otomatis — untuk berkas kecil, tidak ada langkah lain yang diperlukan.
Scanner membaca hemat memori: satu baris dipegang pada satu waktu, sehingga berkas berukuran gigabyte pun aman disusuri. Pola Open + defer Close + Scan ini adalah bentuk paling sering kamu tulis saat mengolah log atau CSV.
MkdirAll membuat seluruh jalur sekaligus — folder induk yang belum ada ikut dibuat tanpa error. ReadDir mendaftar isi folder: tiap entri punya Name dan IsDir untuk memilah berkas dari subfolder.
Uji: tulis dua baris ke catatan.txt, baca ulang, cetak jumlah barisnya — program harus mencetak persis `2`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"os"
)
func main() {
if err := os.WriteFile("catatan.txt", []byte("baris pertama\nbaris kedua"), 0644); err != nil {
fmt.Println("gagal menulis:", err)
return
}
isi, err := os.ReadFile("catatan.txt")
if err != nil {
fmt.Println("gagal membaca:", err)
return
}
fmt.Print(string(isi))
}Bangun server HTTP murni standard library.
Paket net/http membangun server web tanpa framework apa pun. Resep minimumnya: daftarkan handler lewat http.HandleFunc(rute, fungsi), lalu jalankan http.ListenAndServe(":8080", nil). Setiap permintaan ke rute diarahkan ke fungsinya.
Handler menerima dua nilai: w http.ResponseWriter untuk menulis respons (Fprintln(w, ...) mencetak ke browser), dan r *http.Request berisi segalanya tentang permintaan — metode, header, query, hingga body. Rute "/" menangkap semua yang tidak terdaftar; rute yang lebih panjang dicocokkan persis.
Buka localhost:8080 di browser atau curl localhost:8080 untuk melihat hasilnya. Kesalahan ListenAndServe dikembalikan saat server berhenti — biasanya ditutup dengan log.Fatal. Dari kerangka inilah seluruh pelajaran web berikutnya tumbuh: JSON, form, hingga middleware.
Dua rute terdaftar: "/" menjawab semua alamat lain, "/tentang" khusus. Fprintln ke w menulis langsung ke browser pemanggil. Jalankan lalu buka localhost:8080 dan localhost:8080/tentang — perhatikan header Content-Type otomatis terpasang sebagai text/plain; pelajaran JSON mengubahnya.
Query string (localhost:8080/sapa?nama=Andi) dibaca lewat r.URL.Query().Get — Get mengembalikan string kosong bila parameternya absen, jadi nilai cadangan mudah dipasang. Inilah interaksi pertama request → logika → response.
Bedah alamat URL menjadi komponen-komponennya.
net/url mengurai alamat menjadi struktur teratur. url.Parse(teks) menghasilkan *url.URL dengan field siap baca: Scheme (https), Host (domain:port), Path (jalur), RawQuery (query mentah), Fragment (#bagian). Parse juga memvalidasi — alamat cacat mengembalikan error.
Query adalah bagian yang paling sering diolah: u.Query() mengubahnya menjadi url.Values — map[string][]string dengan method Get(kunci) untuk nilai pertama. Karena satu parameter bisa muncul berkali (?a=1&a=2), nilainya memang berbentuk slice; Encode() mengemas balik menjadi query string rapi dengan escaping benar.
Konstruksi arah sebaliknya juga mulus: buat url.Values, isi, lalu Encode untuk menyematkan pada URL keluar — cara aman membangun query tanpa menyusun string manual dan lupa escape karakter seperti spasi atau &.
Tiga bagian alamat tercetak terpisah: https, api.contoh.dev, /v1/cari. Get mengambil nilai tunggal (q=golang, halaman=2) sedangkan akses map langsung q["tag"] menghasilkan seluruh nilai berulang [web cli] — pilihan bentuk mengikuti kebutuhan.
Spasi pada "belajar go gratis" otomatis menjadi + dan pasangan diurutkan — Encode menjamin query valid tanpa kamu menghafal aturan escaping. Menyusun query lewat Values selalu lebih aman daripada merangkai string + "?" + teks mentah.
Uji: ambil nilai `q` dan tag KEDUA — program harus mencetak persis `golang cli`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"net/url"
)
func main() {
u, err := url.Parse("https://api.contoh.dev/v1/cari?q=golang&halaman=2&tag=web&tag=cli")
if err != nil {
return
}
fmt.Println(u.Scheme, u.Host, u.Path)
q := u.Query()
fmt.Println(q.Get("q"), q.Get("halaman"), q["tag"])
}Ubah struct menjadi JSON dan sebaliknya.
encoding/json menghubungkan dunia struct Go dengan format JSON. Marshal(v) mengubah nilai menjadi []byte JSON; Unmarshal(data, &v) mengisi struct dari JSON. Kunci-kuncinya diambil dari nama field — kecuali diubah lewat struct tag `json:"nama_lain"`.
Tag mengatur lebih dari nama: `json:"nama,omitempty"` menghapus field saat kosong, `json:"-"` menyembunyikannya total. MarshalIndent menambah indentasi untuk hasil yang enak dibaca manusia. Aturan penting: hanya FIELD DIEKSPOR (huruf awal kapital) yang diserialisasi — json membacanya lewat refleksi dari luar paket.
Unmarshal mengisi struct yang kamu sediakan alamatnya (&s). Field JSON yang tak dikenal diabaikan diam-diam; field struct tanpa pasangan dibiarkan bernilai nol. Untuk aliran data besar, json.NewDecoder(r).Decode(&v) membaca langsung dari io.Reader seperti body permintaan HTTP.
Tag json:"nama" mengganti Nama menjadi nama kecil di keluaran. Email kosong hilang berkat omitempty — ari tidak punya email sehingga fieldnya tidak muncul. Unifikasi balik: Unmarshal mengisi struct budi dari teks JSON mentah; field tanpa pasangan tetap nol.
Struct bersarang dan slice dipetakan otomatis ke objek dan array JSON — bentuk data selama seumur hidupmu sudah dicover. Slice bertipe struct (Anggota []Siswa) menerima array objek apa pun panjangnya.
Uji: tambahkan tag `json:"diskon,omitempty"` agar field kosong hilang — program harus mencetak persis `{"nama":"Kopi","harga":18000}`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"encoding/json"
"fmt"
)
type Siswa struct {
Nama string `json:"nama"`
Poin int `json:"poin"`
Email string `json:"email,omitempty"`
}
func main() {
ari := Siswa{Nama: "Ari", Poin: 95}
data, _ := json.Marshal(ari)
fmt.Println(string(data)) // {"nama":"Ari","poin":95}
var budi Siswa
json.Unmarshal([]byte(`{"nama":"Budi","poin":80}`), &budi)
fmt.Printf("%+v\n", budi)
}Bangun endpoint yang menyajikan JSON.
API server adalah web server yang jalannya sama dengan JSON: handler menerima permintaan, memprosesnya, dan membalas body JSON dengan status serta header yang tepat. Kerangkanya: atur w.Header().Set("Content-Type", "application/json"), marshal data, w.Write(keluaran).
r.Method memisahkan GET dan POST pada rute yang sama — pola REST dasar. POST membaca body lewat json.NewDecoder(r.Body).Decode(&masukan), mengisi struct dari JSON kiriman klien. Kesalahan (bad request, data tak ditemukan) diwakili status code: w.WriteHeader(http.StatusBadRequest) sebelum menulis body error dalam bentuk JSON juga.
Berkas utama server nyata biasanya memisahkan handler per entitas dan mendaftarkan banyak rute; kerangkanya tetap persis tiga langkah ini: header → status → body. Uji dengan curl: curl localhost:8080/api/siswa dan curl -X POST -d '{"nama":"Citra"}' localhost:8080/api/siswa.
Satu rute, dua perilaku: GET mengembalikan daftar siswa sebagai JSON (NewEncoder menulis langsung ke response), POST menambahkan siswa baru dan menjawab 201 Created. Body yang bukan JSON valid ditolak 400 dengan pesan error JSON. json.NewEncoder(w) menggabungkan marshal+tulis dalam satu langkah.
Rute dengan akhiran "/" menangkap semua sub-pathnya; potongan setelah prefix diambil dengan TrimPrefix sebagai identitas. Tak ketemu → 404. (Sejak Go 1.22 ada r.PathValue("nama") dengan pola "/api/siswa/{nama}" yang jauh lebih bersih — bentuk manual ini menunjukkan mesin di baliknya.)
Konsumsi API dari sisi klien.
Sisi klien juga tinggal satu baris: resp, err := http.Get(alamat) melakukan permintaan GET dan mengembalikan respons berisi StatusCode, Header, dan Body. Body adalah stream — baca dengan io.ReadAll lalu TUTUP dengan defer resp.Body.Close(); lupa menutup membuat koneksi menumpuk dan bocor.
Untuk metode lain dan body kiriman, bangun permintaan eksplisit: http.NewRequest(http.MethodPost, alamat, bytes.NewReader(dataJson)), pasang header r.Header.Set("Content-Type", "application/json"), lalu kirim lewat http.DefaultClient.Do(r). Pola yang sama mengirim token auth atau API key di header.
Status 4xx/5xx BUKAN error Go — permintaan sukses dijalankan, server menolak. Periksa resp.StatusCode sendiri dan ubah menjadi error aplikasimu. Timeout klien juga diatur manual: buat &http.Client{Timeout: 10 * time.Second} alih-alih memakai DefaultClient yang menunggu tanpa batas.
Get menjalankan permintaan penuh: DNS, TCP, TLS, lalu menyerahkan respons. err hanya muncul untuk kegagalan jaringan — server menjawab 404 tetap dianggap sukses secara transport. Body wajib ditutup; tanpa defer Close, program intensif API pelan-pelan kehabisan koneksi.
NewRequest menyiapkan metode, alamat, dan body (bytes.NewReader membungkus JSON jadi io.Reader). Header dipasang pada req sebelum Do mengirimkannya. Pemeriksaan StatusCode mengubah penolakan server menjadi error yang bisa ditangani — pemisahan "gagal kirim" vs "ditolak" ini penting untuk retry logic.
Uji: buat server uji lokal dengan httptest, lalu GET darinya — program harus mencetak persis `200`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
fmt.Println("gagal:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("status:", resp.StatusCode)
fmt.Println("panjang body:", len(body), "byte")
}Sambungkan database SQL dengan database/sql.
Antarmuka database/sql adalah lapisan standar Go untuk database relasional; drivernya dipasang terpisah per database — modernc.org/sqlite untuk SQLite tanpa CGO, go-sql-driver/mysql untuk MySQL, lib/pq untuk Postgres. Driver didaftarkan lewat import dengan efek samping (_ "driver"), lalu sql.Open(namaDriver, sumberData) menyiapkan pool koneksi.
Query mengembalikan baris: rows, err := db.Query(sql) lalu for rows.Next() { rows.Scan(&kolom...) } dan rows.Err() di akhir. QueryRow untuk satu baris, Exec untuk pernyataan tanpa hasil (INSERT/UPDATE/DELETE) yang melaporkan RowsAffected. Parameter selalu placeholder (? atau $1) — NEVER rangkai string SQL, itu pintu injeksi.
db.Ping memverifikasi koneksi nyata (Open malas — baru benar-benar connect saat dipakai). Prepared statement lewat db.Prepare dipakai berulang untuk operasi berparameter dalam jumlah banyak. Konvensi umum: satu objek db dibagi seluruh aplikasi, bukan dibuka-tutup per permintaan.
Import driver dengan _ karena efeknya pendaftaran, bukan pemakaian langsung. Placeholder ? menjaga nilai tetap data, bukan kode SQL. Loop Query: Next maju per baris, Scan menyalin kolom ke variabel — selalu tutup rows (dan cek rows.Err) agar koneksi kembali ke pool.
Simpan dokumen JSON-style dengan driver Mongo.
MongoDB adalah database dokumen: satu baris = satu dokumen BSON yang bentuknya mirip JSON dengan field bebas, cocok untuk data yang skemanya berkembang. Driver resminya go.mongodb.org/mongo-driver — koneksi lewat mongo.Connect(client.Options{ApplyURI(...)}), lalu collection diambil dari database dengan mongoClient.Database("nama").Collection("nama").
Operasi dasar: InsertOne/InsertMany menulis dokumen; FindOne dengan bson.M{"nama": nilai} mengambil satu; Find mengembalikan cursor yang disusuri seperti rows SQL (cursor.Next + cursor.Decode). UpdateOne mengubah field dengan operator $set; DeleteOne menghapus.
Filter ditulis dengan tipe bson.M — map bebas kunci-nilai yang diterjemahkan ke BSON: bson.M{"umur": bson.M{"$gte": 18}} berarti umur >= 18. Kontras dengan SQL: tak ada skema tabel yang harus cocok; dokumen dalam satu koleksi boleh berbeda bentuk, dan struct Go tetap bisa menampilkannya lewat tag bson:"nama" mirip json.
Koneksi memakai URI standar mongodb://host:port; collection diambil tanpa migrasi — database dan koleksi dibuat otomatis saat pertama diisi. FindOne menerima filter bson.M lalu Decode mengisi struct lewat tag bson. Context TODO di contoh; di produksi gunakan context dengan timeout.
bson.M bersarang menyusun operator ($gte, $in, $regex) seperti klausa WHERE yang dirakit dari potongan. SetSort mengatur urutan, cursor.All mengumpulkan seluruh hasil sekaligus, dan $inc menaikkan nilai tanpa membaca-dulu-menulis-belakangan — operasi atomik di server.
Uji fungsi otomatis dengan paket testing.
Tes Go hidup di berkas berakhiran _test.go di samping kode yang diuji. Setiap tes adalah fungsi bernama TestXxx (awalan Test wajib) yang menerima *testing.T. Jalankan semuanya dengan go test ./... — hasilnya lulus/gagal per paket, plus go test -v untuk rincian.
Kegagalan dilaporkan lewat t.Error / t.Errorf (menandai gagal tapi lanjut) atau t.Fatal / t.Fatalf (berhenti di titik itu). Assert bawaan memang tak ada — pola idiomatisnya if hasil != harapan { t.Errorf(...) } yang menampilkan dua nilai bersebelahan.
Bentuk dewasa di komunitas Go: table-driven test — semua kasus didefinisikan sebagai slice struct {nama, masukan, harapan}, lalu satu loop menjalankan semuanya lewat t.Run(nama, ...) yang membuat subtes terpisah. Menambah kasus baru cukup menambah baris tabel, tanpa menyalin logika. Subtes bisa dijalankan tunggal: go test -run TestBagi/pembagi_nol.
Berkas matematik_test.go menguji fungsi di paket yang sama. t.Errorf menandai kegagalan TANPA menghentikan tes — beberapa kasus bisa diperiksa berurutan dan semuanya terlapor sekali jalan. Pesannya mencantumkan hasil dan harapan agar kegagalan langsung terbaca penyebabnya.
Empat kasus hidup dalam satu tabel; loop menjalankan masing-masing sebagai subtes bernama. Kasus "pembagi nol" menguji jalur error eksplisit — error yang DIHARAPkan adalah bagian kontrak fungsi, bukan kecelakaan. Tambahkan kasus baru dengan menambah baris, dan -run memungkinkan mengeksekusi hanya satu.
Uji: jalankan empat kasus pada tabel dan bandingkan dengan harapan — program harus mencetak persis `4/4 lulus`.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package matematik
import "testing"
func Bagi(a, b int) (int, error) {
if b == 0 {
return 0, ErrPembagiNol
}
return a / b, nil
}
var ErrPembagiNol = errPembagiNol{}
type errPembagiNol struct{}
func (errPembagiNol) Error() string { return "pembagi tidak boleh nol" }
func TestBagi(t *testing.T) {
hasil, err := Bagi(10, 2)
if err != nil || hasil != 5 {
t.Errorf("Bagi(10,2) = %d, %v; ingin 5, nil", hasil, err)
}
}Tunggu sekumpulan goroutine dan lindungi data bersama.
sync.WaitGroup adalah alat hitung-mundur untuk menunggu banyak goroutine: Add(n) menambah penghitung sebelum meluncurkan, Done() di tiap goroutine menurunkannya (biasanya lewat defer), dan Wait() memblokir sampai nol. Inilah pengganti time.Sleep yang benar saat channel tidak diperlukan.
Ketika banyak goroutine MENULIS variabel yang sama, balapan data terjadi: dua penambahan berbarengan saling menimpa, hasil akhir lebih kecil dari seharusnya. Deteksinya mudah: go test -race atau go run -race menampilkan laporan race detector. Solusinya sync.Mutex — kunci sebelum menyentuh data, buka setelahnya (defer mu.Unlock()).
Channel dan mutex menyelesaikan masalah berbeda: channel memindahkan kepemilikan data antar goroutine (berkomunikasi), mutex melindungi akses ke satu memori bersama. Prinsip Rob Pike yang terkenal: "jangan berkomunikasi dengan berbagi memori; bagikan memori dengan berkomunikasi" — tapi keduanya sama-sama sah dan saling melengkapi.
Tanpa mutex, hasil bisa 972 atau 989 — dua goroutine membaca nilai sama lalu menulis hasil sama, satu penambahan lenyap. Lock/Unlock membuat total++ atomik: satu penghitung masuk, lainnya antre. wg.Wait memastikan pencetakan menunggu seluruh 1000 goroutine tuntas. Hilangkan mu dan jalankan go run -race untuk melihat laporan balapannya.
Bila goroutine tidak berbagi variabel, mutex tidak diperlukan — WaitGroup saja cukup sebagai pembatas akhir. Add dipanggil SEBELUM go (bukan di dalam goroutine) agar penghitung tidak pernah sempat nol lebih awal — jebakan umum yang membuat Wait lolos terlalu cepat.
Uji: seribu goroutine menambah counter — dengan mutex program harus mencetak persis `1000`. Lalu hapus mutex dan lihat hasilnya goyang.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
total := 0
var mu sync.Mutex
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
total++ // bagian kritis: satu-satunya penulis diizinkan
mu.Unlock()
}()
}
wg.Wait()
fmt.Println(total) // selalu 1000
}Sematkan dependensi ke dalam proyek agar build mandiri.
go mod vendor menyalin seluruh paket yang dipakai proyek — beserta seluruh versi persisnya — ke folder vendor/ di dalam repo. Setelah itu go build otomatis memakai folder itu dan mengabaikan cache modul: proyek bisa dibangun tanpa mengunduh apa pun dari internet.
Gunanya: build yang reprodusibel di lingkungan terkunci (CI tanpa akses luar, server on-premise, review keamanan yang mewajibkan semua kode berada dalam repo). Commit folder vendor berarti setiap orang — dan setiap mesin — memegang bit yang identik.
Sisi beratnya jelas: repo membengkak (vendor bisa puluhan-hingga-ratusan MB) dan tiap pembaruan dependensi ikut menyimpan diff besar. Karena itu keputusannya pragmatis: proyek biasa cukup go.mod + go.sum; vendoring saat lingkungan build benar-benar terputus atau kebijakan menuntutnya. Perintah go mod vendor dijalankan ulang setiap kali dependensi berubah, dan flag -mod=vendor memaksa mode itu eksplisit.
Setelah vendor dijalankan, folder vendor/ memuat salinan persis seluruh pustaka yang diimpor proyek (hanya yang benar-benar dipakai — paket tak terpakai tidak ikut). Commit folder itu, dan mesin tanpa internet pun bisa membangun binary identik.
Rangkai tahap goroutine dan batalkan lewat context.
Pipeline adalah goroutine-goroutine yang terhubung channel: tahap pertama MEMBUAT data (generator), tahap-tahap berikut MENGOLAH dan meneruskan hasilnya lewat channel keluaran. Tiap tahap berjalan bersamaan — sambil satu tahap menghitung, yang lain sudah menyiapkan berikutnya. Kunci polanya: setiap tahap menutup channel keluarannya setelah selesai, dan for range di hilir otomatis berhenti.
Fan-in/fan-out memperbanyak jalur: beberapa tahap pekerja yang SAMA membaca dari satu channel input (fan-out — memparalelkan kerja), hasilnya digabung ke satu channel (fan-in). Dengan begitu pekerjaan lambat dibagi ke semua core tanpa mengubah bentuk kodenya.
Pembatalan elegan lewat context.Context: context.WithCancel membuat channel Done yang tertutup saat cancel dipanggil. Setiap tahap memilih antara channel kerja dan ctx.Done — begitu sinyal pembatalan tiba, seluruh pipeline angkat kaki dengan rapi. Varian WithTimeout memasang pembatalan otomatis berbasis waktu; pola ini wajib di API modern Go.
Dua tahap terangkai: generator mengirim 1-4 lalu menutup; kuadrat menerima lewat range, mengalikan, meneruskan, lalu ikut menutup. main hanya membaca hasil akhir — penutupan berantai membuat seluruh pipeline mati dengan rapi setelah data habis. Tanda panah pada tipe (<-chan) membatasi channel jadi hanya-keluar: konsumen tak bisa mengirim balik.
select menjunjung dua kemungkinan: kirim nilai berikutnya, atau berhenti saat ctx.Done tertutup. cancel() menutup Done seketika dan generator pulang — goroutine tidak menggantung menunggu konsumen yang sudah pergi. context.WithTimeout(ctx, 2*time.Second) memakai bentuk yang sama dengan alarm otomatis.
Uji: sambung pipeline filter-genap → kuadrat, terima tiga hasil lalu batalkan — harus `4`, `16`, `36`, `selesai` (empat baris).
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func generator(angka ...int) <-chan int {
keluar := make(chan int)
go func() {
defer close(keluar)
for _, n := range angka {
keluar <- n
}
}()
return keluar
}
func kuadrat(masuk <-chan int) <-chan int {
keluar := make(chan int)
go func() {
defer close(keluar)
for n := range masuk {
keluar <- n * n
}
}()
return keluar
}
func main() {
for hasil := range kuadrat(generator(1, 2, 3, 4)) {
fmt.Println(hasil) // 1 4 9 16
}
}Tulis fungsi dan tipe yang bekerja untuk banyak tipe.
Sebelum Go 1.18, fungsi serba-guna memakai interface{} dan kehilangan informasi tipe — atau menulis salinan kode per tipe. Generics mengubah itu: daftar parameter tipe ditulis dalam kurung siku, func Jumlah[T int | float64](angka []T) T, dan T menjadi tipe sungguhan di dalam badan fungsi — pemeriksaan kompilator tetap penuh.
T dibatasi lewat constraint: union tipe seperti int | float64, constraint bernama yang berisi kumpulan tipe berurutan, atau comparable untuk tipe yang mendukung ==. Constraint menentukan operasi apa yang boleh dilakukan pada T.
Selain fungsi, tipe juga generik: type Stack[T any] struct{ items []T } — satu definisi stack untuk semua tipe tanpa assertion. Saat memakainya, kompiler menginstansiasi: Jumlah([]int{...}) menyimpulkan T = int otomatis; Stack[string] ditulis eksplisit untuk tipe.
Satu definisi Jumlah melayani dua tipe — total dideklarasikan bertipe T sehingga nilainya nol yang benar per tipe. Pemanggil tak perlu menyebut T: kompiler menyimpulkannya dari argumen. Versi any akan butuh type assertion dan konversi manual di dalam; versi generik tetap type-safe penuh.
Stack[T] menyimpan tipe apa pun dengan TIPEnya tetap terjaga — Pop mengembalikan (T, bool), bukan (any, bool), sehingga pemakai tak perlu assertion. Receiver juga membawa parameter tipe: (s *Stack[T]). Ini menggantikan pola interface{} lama untuk struktur data generik.
Constraint bernama merangkum keluarga tipe — Number mencakup int, int64, float64 beserta tipe turunannya (tanda ~). Fungsi ber constraint ini menerima semuanya tanpa duplikasi, dan badan fungsinya hanya boleh memakai operasi yang dijamin constraint (di sini: + dan /).
Uji: buat Map generik untuk len() dan Queue[T] dengan Enqueue/Dequeue — harus `[2 6 2]` lalu `a b`.
Generics memerlukan Go 1.18 atau lebih baru. Compiler situs memakai Go 1.13.5; salin kode latihan dan jalankan dengan Go versi baru di komputermu atau di Go Playground.
Sandbox Judge0 memakai Go 1.13.5. Fitur Go yang lebih baru atau program dengan banyak berkas perlu dicoba di runtime lokal.
package main
import "fmt"
func Jumlah[T int | float64](angka []T) T {
var total T
for _, n := range angka {
total += n
}
return total
}
func main() {
fmt.Println(Jumlah([]int{1, 2, 3})) // 6
fmt.Println(Jumlah([]float64{1.5, 2.5})) // 4
}