Array #
Array di Go adalah struktur data yang ukurannya ditentukan saat kompilasi dan tidak bisa berubah. Dalam praktik sehari-hari, developer Go lebih sering menggunakan slice — tapi ini bukan berarti array tidak berguna. Justru, memahami array dengan benar adalah kunci memahami bagaimana slice bekerja di baliknya, karena slice pada dasarnya adalah “jendela” ke sebuah array. Ada juga kasus-kasus spesifik di mana array adalah pilihan yang lebih tepat dari slice: fixed-size buffer, lookup table berukuran tetap, dan matriks. Artikel ini membahas array dari yang paling dasar hingga hubungannya yang fundamental dengan slice.
Array adalah Value Type dengan Ukuran sebagai Bagian Tipe #
Ini adalah hal paling penting yang perlu dipahami tentang array di Go: ukuran array adalah bagian dari tipenya. [3]int dan [5]int adalah dua tipe yang berbeda — seperti int dan string berbeda:
var a [3]int
var b [5]int
// a = b // ← compile error: cannot use b (type [5]int) as type [3]int
// Fungsi yang menerima [3]int tidak bisa menerima [5]int
func sum3(arr [3]int) int { ... }
sum3(a) // ✓
sum3(b) // ✗ compile error
Implikasi praktisnya: fungsi yang menerima array harus menyebutkan ukurannya secara eksplisit, yang membuatnya sangat tidak fleksibel. Inilah salah satu alasan utama slice lebih sering digunakan — slice tidak punya batasan ukuran dalam tipenya.
Deklarasi dan Inisialisasi #
Deklarasi dengan Zero Value #
var a [5]int // [0 0 0 0 0] — semua elemen diinisialisasi ke zero value
var b [3]string // ["" "" ""] — string kosong
var c [4]bool // [false false false false]
var d [2]float64 // [0 0]
fmt.Println(a) // [0 0 0 0 0]
fmt.Println(b) // [ ] — tiga string kosong
Tidak seperti C, array di Go tidak pernah berisi nilai garbage — semua elemen selalu terinisialisasi ke zero value tipenya.
Inisialisasi dengan Literal #
// Semua elemen explicit
primes := [5]int{2, 3, 5, 7, 11}
// Partial — elemen yang tidak disebut mendapat zero value
scores := [5]int{100, 95} // [100 95 0 0 0]
// Inisialisasi dengan index spesifik
sparse := [10]int{0: 1, 5: 10, 9: 100}
// [1 0 0 0 0 10 0 0 0 100]
// String array
hari := [7]string{
"Minggu", "Senin", "Selasa", "Rabu",
"Kamis", "Jumat", "Sabtu",
}
Elipsis [...] — Ukuran Otomatis dari Isi
#
Gunakan [...] untuk membiarkan compiler menghitung ukuran array dari jumlah elemen yang diberikan:
// Ukuran dihitung otomatis: 5 elemen → [5]int
primes := [...]int{2, 3, 5, 7, 11}
fmt.Printf("Tipe: %T, Panjang: %d\n", primes, len(primes))
// Tipe: [5]int, Panjang: 5
// Berguna agar tidak perlu hitung manual
warna := [...]string{
"Merah", "Jingga", "Kuning",
"Hijau", "Biru", "Nila", "Ungu",
}
// [7]string — 7 elemen
Mengakses dan Memodifikasi Elemen #
Akses elemen menggunakan index yang dimulai dari 0:
arr := [5]int{10, 20, 30, 40, 50}
// Baca
fmt.Println(arr[0]) // 10
fmt.Println(arr[4]) // 50
fmt.Println(arr[len(arr)-1]) // 50 — elemen terakhir
// Tulis
arr[2] = 999
fmt.Println(arr) // [10 20 999 40 50]
Bounds Checking — Keamanan Runtime #
Go selalu memeriksa index array saat runtime. Mengakses index di luar rentang menyebabkan panic:
arr := [3]int{1, 2, 3}
fmt.Println(arr[2]) // ✓ 3 — index valid
fmt.Println(arr[3]) // ✗ panic: runtime error: index out of range [3] with length 3
// Index negatif juga panic
fmt.Println(arr[-1]) // ✗ compile error: invalid argument -1 (index must be non-negative)
Tidak seperti C, Go tidak punya buffer overflow. Mengakses array di luar batas menyebabkan panic — program berhenti dengan pesan error yang jelas, bukan perilaku undefined yang membahayakan. Untuk menghindari panic, selalu validasi index sebelum mengakses:
func safeGet(arr [5]int, i int) (int, bool) { if i < 0 || i >= len(arr) { return 0, false } return arr[i], true }
Array adalah Value Type — Copy Semantics #
Array di Go adalah value type — ketika kamu assign array ke variabel lain atau pass ke fungsi, Go membuat salinan lengkap dari semua elemen:
a := [3]int{1, 2, 3}
b := a // b adalah SALINAN LENGKAP dari a
b[0] = 999
fmt.Println(a) // [1 2 3] — tidak berubah
fmt.Println(b) // [999 2 3]
Implikasi untuk Fungsi #
Karena array di-pass by value, fungsi menerima salinan — modifikasi dalam fungsi tidak mempengaruhi array asli:
// Fungsi ini memodifikasi SALINAN, bukan array asli
func doubleAll(arr [5]int) [5]int {
for i := range arr {
arr[i] *= 2
}
return arr // kembalikan salinan yang sudah dimodifikasi
}
func main() {
original := [5]int{1, 2, 3, 4, 5}
doubled := doubleAll(original)
fmt.Println(original) // [1 2 3 4 5] — tidak berubah
fmt.Println(doubled) // [2 4 6 8 10]
}
// Jika ingin memodifikasi asli, gunakan pointer
func doubleAllInPlace(arr *[5]int) {
for i := range arr {
arr[i] *= 2
}
}
func main() {
arr := [5]int{1, 2, 3, 4, 5}
doubleAllInPlace(&arr)
fmt.Println(arr) // [2 4 6 8 10] — berubah!
}
Untuk array besar, copy semantics ini bisa menjadi bottleneck performa. Solusinya: gunakan pointer ke array, atau (lebih idiomatik) gunakan slice.
Perbandingan Array #
Array bisa dibandingkan dengan == dan != jika tipe elemennya comparable dan ukurannya sama:
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
c := [3]int{1, 2, 4}
fmt.Println(a == b) // true — semua elemen sama
fmt.Println(a == c) // false — elemen terakhir berbeda
fmt.Println(a != c) // true
// Tipe berbeda tidak bisa dibandingkan
d := [4]int{1, 2, 3, 4}
// fmt.Println(a == d) // ← compile error: mismatched types [3]int and [4]int
// Array dengan elemen non-comparable tidak bisa dibandingkan
e := [2][]int{{1, 2}, {3, 4}}
f := [2][]int{{1, 2}, {3, 4}}
// fmt.Println(e == f) // ← compile error: [2][]int is not comparable
_ = e
_ = f
Kemampuan membandingkan array ini berguna untuk hash map key — array bisa digunakan sebagai key map:
// Array sebagai map key — berguna untuk coordinate mapping
type Point [2]int
grid := map[Point]string{
{0, 0}: "origin",
{1, 0}: "east",
{0, 1}: "north",
}
fmt.Println(grid[Point{0, 0}]) // "origin"
fmt.Println(grid[Point{1, 0}]) // "east"
Iterasi #
arr := [5]int{10, 20, 30, 40, 50}
// Classic for
for i := 0; i < len(arr); i++ {
fmt.Printf("arr[%d] = %d\n", i, arr[i])
}
// for-range — lebih idiomatik
for i, v := range arr {
fmt.Printf("arr[%d] = %d\n", i, v)
}
// Hanya value
for _, v := range arr {
fmt.Println(v)
}
// Hanya index
for i := range arr {
arr[i] *= 2 // modifikasi via index (range memberi salinan value)
}
fmt.Println(arr) // [20 40 60 80 100]
// Iterasi terbalik
for i := len(arr) - 1; i >= 0; i-- {
fmt.Println(arr[i])
}
Array Multidimensi #
Go mendukung array dengan lebih dari satu dimensi. Paling umum adalah array dua dimensi untuk matriks:
// Array 2D — deklarasi
var matrix [3][4]int // 3 baris, 4 kolom — zero value semua
// Inisialisasi
grid := [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
// Akses elemen: [baris][kolom]
fmt.Println(grid[0][0]) // 1 — baris 0, kolom 0
fmt.Println(grid[1][2]) // 6 — baris 1, kolom 2
fmt.Println(grid[2][2]) // 9 — baris 2, kolom 2
// Modifikasi
grid[1][1] = 99
fmt.Println(grid[1][1]) // 99
Traversal Matriks #
// Cetak matriks dengan format rapi
func printMatrix(m [3][3]int) {
for i, row := range m {
for j, val := range row {
fmt.Printf("%3d", val)
if j < len(row)-1 {
fmt.Print(" ")
}
}
fmt.Println()
_ = i
}
}
// Transpose matriks (tukar baris dan kolom)
func transpose(m [3][3]int) [3][3]int {
var result [3][3]int
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
result[j][i] = m[i][j]
}
}
return result
}
// Perkalian matriks
func multiply(a, b [3][3]int) [3][3]int {
var result [3][3]int
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
for k := 0; k < 3; k++ {
result[i][j] += a[i][k] * b[k][j]
}
}
}
return result
}
Array 3D #
// RGB image sebagai array 3D: [tinggi][lebar][3]uint8
var image [480][640][3]uint8
// Set piksel di (100, 200) ke warna merah
image[100][200][0] = 255 // R
image[100][200][1] = 0 // G
image[100][200][2] = 0 // B
Hubungan Array dan Slice #
Ini adalah konsep yang paling penting untuk dipahami. Slice adalah “jendela” ke sebuah array. Setiap slice memiliki backing array di baliknya. Ketika kamu membuat slice dari array, keduanya berbagi memori yang sama:
arr := [5]int{10, 20, 30, 40, 50}
// Buat slice dari array — berbagi backing array!
s := arr[1:4] // slice berisi {20, 30, 40}
fmt.Println(arr) // [10 20 30 40 50]
fmt.Println(s) // [20 30 40]
// Modifikasi melalui slice MENGUBAH array asli!
s[0] = 999
fmt.Println(arr) // [10 999 30 40 50] — array berubah!
fmt.Println(s) // [999 30 40]
// Modifikasi melalui array juga mengubah slice
arr[2] = 777
fmt.Println(arr) // [10 999 777 40 50]
fmt.Println(s) // [999 777 40] — slice berubah!
Memahami hubungan ini adalah kunci memahami perilaku slice yang akan dibahas lebih dalam di artikel berikutnya.
Kapan Gunakan Array vs Slice #
Ini adalah pertanyaan yang sering muncul. Panduan praktisnya:
GUNAKAN ARRAY jika:
✓ Ukuran benar-benar tetap dan diketahui saat kompilasi
✓ Ukuran kecil dan copy semantics tidak jadi masalah performa
✓ Butuh array sebagai map key (slice tidak bisa jadi key)
✓ Implementasi algoritma matriks dengan ukuran tetap
✓ Fixed-size buffer di level rendah (byte buffer untuk protokol)
✓ Tipe alias seperti [16]byte untuk UUID atau [32]byte untuk hash
GUNAKAN SLICE untuk semua kasus lain:
✓ Ukuran tidak diketahui saat kompilasi
✓ Perlu menambah atau menghapus elemen
✓ Fungsi yang harus bekerja dengan berbagai ukuran koleksi
✓ Hampir semua operasi koleksi sehari-hari
Aturan praktis: mulai dengan slice. Beralih ke array hanya
jika ada alasan teknis yang spesifik.
Use Case Nyata Array #
Fixed-Size Identifier #
// UUID — selalu 16 byte
type UUID [16]byte
func NewUUID() UUID {
var id UUID
rand.Read(id[:]) // isi dengan random bytes
return id
}
// SHA-256 hash — selalu 32 byte
type Hash [32]byte
func hashData(data []byte) Hash {
return sha256.Sum256(data) // mengembalikan [32]byte
}
Lookup Table #
// Nama bulan — ukuran tetap 12
var bulan = [12]string{
"Januari", "Februari", "Maret", "April",
"Mei", "Juni", "Juli", "Agustus",
"September", "Oktober", "November", "Desember",
}
func namaBulan(m int) string {
if m < 1 || m > 12 {
return "Tidak valid"
}
return bulan[m-1]
}
// Jumlah hari per bulan (non-leap year)
var daysInMonth = [12]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
Fixed-Size Buffer untuk Protokol Jaringan #
// Header HTTP/2 frame — selalu 9 byte
type FrameHeader [9]byte
func (h FrameHeader) Length() int {
return int(h[0])<<16 | int(h[1])<<8 | int(h[2])
}
func (h FrameHeader) Type() byte {
return h[3]
}
func (h FrameHeader) Flags() byte {
return h[4]
}
Contoh Program Lengkap #
Program berikut mengimplementasikan berbagai operasi matriks menggunakan array multidimensi:
package main
import (
"fmt"
"math"
)
const N = 3 // ukuran matriks
type Matrix [N][N]float64
// Buat matriks identitas
func identity() Matrix {
var m Matrix
for i := 0; i < N; i++ {
m[i][i] = 1 // diagonal = 1, sisanya 0 (zero value)
}
return m
}
// Tambahkan dua matriks
func add(a, b Matrix) Matrix {
var result Matrix
for i := 0; i < N; i++ {
for j := 0; j < N; j++ {
result[i][j] = a[i][j] + b[i][j]
}
}
return result
}
// Kalikan dua matriks
func multiply(a, b Matrix) Matrix {
var result Matrix
for i := 0; i < N; i++ {
for j := 0; j < N; j++ {
for k := 0; k < N; k++ {
result[i][j] += a[i][k] * b[k][j]
}
}
}
return result
}
// Transpose — tukar baris dan kolom
func transpose(m Matrix) Matrix {
var result Matrix
for i := 0; i < N; i++ {
for j := 0; j < N; j++ {
result[j][i] = m[i][j]
}
}
return result
}
// Hitung trace — jumlah diagonal utama
func trace(m Matrix) float64 {
sum := 0.0
for i := 0; i < N; i++ {
sum += m[i][i]
}
return sum
}
// Frobenius norm — ukuran "besar" matriks
func frobeniusNorm(m Matrix) float64 {
sum := 0.0
for i := 0; i < N; i++ {
for j := 0; j < N; j++ {
sum += m[i][j] * m[i][j]
}
}
return math.Sqrt(sum)
}
// Kalikan matriks dengan skalar
func scale(m Matrix, s float64) Matrix {
var result Matrix
for i := 0; i < N; i++ {
for j := 0; j < N; j++ {
result[i][j] = m[i][j] * s
}
}
return result
}
// Cetak matriks dengan format rapi
func print(label string, m Matrix) {
fmt.Printf("%s:\n", label)
for _, row := range m {
fmt.Print(" [")
for j, val := range row {
if j > 0 {
fmt.Print(", ")
}
fmt.Printf("%6.1f", val)
}
fmt.Println("]")
}
}
// Bandingkan dua matriks (dengan toleransi floating point)
func equal(a, b Matrix, epsilon float64) bool {
for i := 0; i < N; i++ {
for j := 0; j < N; j++ {
if math.Abs(a[i][j]-b[i][j]) > epsilon {
return false
}
}
}
return true
}
func main() {
// Matriks A dan B
A := Matrix{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
B := Matrix{
{9, 8, 7},
{6, 5, 4},
{3, 2, 1},
}
I := identity()
fmt.Println("=== Operasi Matriks ===\n")
print("A", A)
fmt.Println()
print("B", B)
fmt.Println()
print("I (Identitas)", I)
fmt.Println()
// Operasi dasar
print("A + B", add(A, B))
fmt.Println()
print("A × B", multiply(A, B))
fmt.Println()
print("Transpose(A)", transpose(A))
fmt.Println()
print("2 × A", scale(A, 2))
fmt.Println()
// Properti
fmt.Printf("Trace(A) = %.1f\n", trace(A))
fmt.Printf("Frobenius Norm(A) = %.4f\n", frobeniusNorm(A))
fmt.Println()
// Verifikasi properti matriks
// A × I = A
AI := multiply(A, I)
fmt.Printf("A × I == A = %v\n", equal(AI, A, 1e-9))
// (A^T)^T = A
ATT := transpose(transpose(A))
fmt.Printf("Transpose(Transpose(A)) == A = %v\n", equal(ATT, A, 1e-9))
// Trace(A^T) = Trace(A)
fmt.Printf("Trace(Transpose(A)) = Trace(A) = %v\n",
math.Abs(trace(transpose(A))-trace(A)) < 1e-9)
fmt.Println()
// Demonstrasi: array sebagai map key
type Coord [2]int
namaLokasi := map[Coord]string{
{0, 0}: "Asal",
{1, 0}: "Timur",
{0, 1}: "Utara",
{-1, 0}: "Barat",
{0, -1}: "Selatan",
}
fmt.Println("=== Koordinat sebagai Map Key ===")
posisi := Coord{1, 0}
if nama, ok := namaLokasi[posisi]; ok {
fmt.Printf("Posisi %v = %s\n", posisi, nama)
}
// Demonstrasi: copy semantics
fmt.Println("\n=== Copy Semantics ===")
original := [3]int{1, 2, 3}
salinan := original // salinan lengkap
salinan[0] = 999
fmt.Printf("Original: %v\n", original) // [1 2 3] — tidak berubah
fmt.Printf("Salinan: %v\n", salinan) // [999 2 3]
// Demonstrasi: slice dari array berbagi memori
fmt.Println("\n=== Array sebagai Backing Storage Slice ===")
arr := [5]int{10, 20, 30, 40, 50}
sl := arr[1:4]
fmt.Printf("Array awal: %v\n", arr)
fmt.Printf("Slice [1:4]: %v\n", sl)
sl[0] = 999
fmt.Printf("Array setelah sl[0]=999: %v\n", arr) // arr berubah!
fmt.Printf("Slice setelah sl[0]=999: %v\n", sl)
}
Ringkasan #
- Ukuran array adalah bagian dari tipenya —
[3]intdan[5]intadalah tipe berbeda; tidak bisa saling dioper ke fungsi yang sama.- Zero value terjamin — semua elemen array selalu terinisialisasi ke zero value tipenya; tidak ada nilai garbage seperti di C.
[...]untuk membiarkan compiler menghitung ukuran dari elemen yang diberikan saat inisialisasi.- Array adalah value type — assignment dan pass ke fungsi membuat salinan lengkap semua elemen.
- Bounds checking runtime — akses index di luar rentang menyebabkan panic dengan pesan yang jelas.
- Array comparable (jika elemennya comparable) — bisa digunakan sebagai map key; berguna untuk koordinat dan identifier tetap.
- Array multidimensi —
[baris][kolom]T; traversal dengan nested loop; berguna untuk matriks dan gambar.- Array adalah backing storage slice — slice yang dibuat dari array berbagi memori dengan array aslinya.
- Gunakan array untuk ukuran tetap yang diketahui saat kompilasi, map key, fixed-size identifier (UUID, hash), dan algoritma matriks.
- Gunakan slice untuk hampir semua kebutuhan koleksi lainnya — lebih fleksibel dan lebih idiomatik di Go.