Slice #

Slice adalah tipe data yang paling sering kamu gunakan di Go — lebih dari array, lebih dari map. Hampir semua koleksi data di Go diekspresikan sebagai slice. Tapi slice bukan sekadar “array yang bisa resize.” Di baliknya ada mekanisme tiga-komponen yang perlu kamu pahami untuk menghindari bug paling halus dan paling sering terjadi di Go: shared backing array. Artikel ini membahas slice dari cara kerjanya di memori hingga semua operasi idiomatik yang dipakai di kode produksi.

Anatomi Slice — Tiga Komponen Internal #

Setiap variabel slice di Go menyimpan tiga field dalam sebuah struct kecil yang disebut slice header. Struktur data internal ini serta hubungannya dengan backing array di memori dapat divisualisasikan sebagai berikut:

flowchart TD
    subgraph SliceHeader ["Slice Header (Struktur Internal)"]
        direction LR
        Ptr["Pointer (Alamat Awal Memori)"]
        Len["Len (Panjang Slice)"]
        Cap["Cap (Kapasitas Slice)"]
    end

    subgraph BackingArray ["Backing Array di Memori"]
        E0["e0"]
        E1["e1"]
        E2["e2"]
        E3["e3"]
        E4["e4"]
        E5["e5"]
        E6["e6"]
        E7["e7"]
    end

    Ptr --> E0
    
    E0 -.->|"Panjang (Len)"| E2
    E0 -.->|"Kapasitas (Cap)"| E7
  • Pointer — menunjuk ke elemen pertama yang “terlihat” oleh slice ini di dalam backing array
  • Len (length) — jumlah elemen yang saat ini ada dalam slice; yang bisa kamu akses dengan index
  • Cap (capacity) — jumlah elemen yang tersedia dari posisi pointer sampai akhir backing array
s := []int{10, 20, 30, 40, 50}
fmt.Println(len(s))  // 5 — panjang
fmt.Println(cap(s))  // 5 — kapasitas = panjang backing array dari pointer

// Sub-slice memindahkan pointer, len berubah, cap berkurang
sub := s[1:3]        // {20, 30}
fmt.Println(len(sub))  // 2
fmt.Println(cap(sub))  // 4 — dari index 1 sampai akhir backing array (5-1=4)

Cara Membuat Slice #

Literal #

// Paling umum untuk data yang sudah diketahui
angka := []int{1, 2, 3, 4, 5}
nama := []string{"Budi", "Sari", "Ahmad"}
kosong := []int{}  // empty slice — bukan nil

make([]T, len, cap) — Pre-alokasi #

Gunakan make ketika panjang atau kapasitas akhir sudah bisa diperkirakan:

// Slice dengan len=5, semua elemen zero value
s1 := make([]int, 5)         // len=5, cap=5
fmt.Println(s1)               // [0 0 0 0 0]

// Slice dengan len=0 tapi cap=100 — siap ditambah sampai 100 elemen tanpa re-alokasi
s2 := make([]int, 0, 100)    // len=0, cap=100
fmt.Println(len(s2), cap(s2)) // 0 100

// Berguna ketika tahu berapa elemen akan ditambah
hasil := make([]int, 0, len(input))
for _, v := range input {
    if v > 0 {
        hasil = append(hasil, v)
    }
}

Dari Array #

arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:4]    // {20, 30, 40} — berbagi backing array dengan arr

Nil Slice vs Empty Slice #

var nilSlice []int    // nil slice — pointer=nil, len=0, cap=0
emptySlice := []int{} // empty slice — pointer non-nil, len=0, cap=0

fmt.Println(nilSlice == nil)    // true
fmt.Println(emptySlice == nil)  // false

// Keduanya punya len=0 dan bisa di-append
fmt.Println(len(nilSlice))   // 0
fmt.Println(len(emptySlice)) // 0

// PERBEDAAN KRITIS: serialisasi JSON
import "encoding/json"

data1, _ := json.Marshal(nilSlice)    // null
data2, _ := json.Marshal(emptySlice)  // []

fmt.Println(string(data1))  // null
fmt.Println(string(data2))  // []
Nil slice vs empty slice menghasilkan JSON yang berbeda. Jika API kamu perlu mengembalikan array kosong (bukan null), pastikan menggunakan []T{} atau make([]T, 0), bukan var s []T. Klien JavaScript yang menerima null alih-alih [] sering mengalami error karena null.map() tidak valid.

Slicing Expression #

Kamu bisa mengambil potongan dari slice atau array menggunakan sintaks s[low:high]:

s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

s1 := s[2:5]    // {2, 3, 4}       — dari index 2 sampai 4 (tidak termasuk 5)
s2 := s[:3]     // {0, 1, 2}       — dari awal sampai index 2
s3 := s[7:]     // {7, 8, 9}       — dari index 7 sampai akhir
s4 := s[:]      // {0,...,9}        — seluruh slice (header baru, backing array sama)

// Aturan: 0 <= low <= high <= cap(s)

Three-Index Slice — Batasi Kapasitas #

Three-index slice s[low:high:max] memungkinkan kamu mengontrol kapasitas slice hasil:

s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

// Two-index: cap mengikuti sisa backing array
s1 := s[2:5]       // len=3, cap=8  (dari index 2 sampai akhir)

// Three-index: cap dibatasi
s2 := s[2:5:6]     // len=3, cap=4  (dari index 2, max index 6)

fmt.Println(len(s1), cap(s1))  // 3 8
fmt.Println(len(s2), cap(s2))  // 3 4

// Kenapa berguna? Append ke s2 tidak akan "merusak" elemen s setelah index 6

Shared Backing Array — Gotcha Terpenting #

Ini adalah sumber bug yang paling umum dengan slice. Ketika kamu melakukan slicing, slice baru berbagi backing array yang sama dengan slice asli:

original := []int{1, 2, 3, 4, 5}
sub := original[1:3]  // {2, 3}

// Modifikasi sub MENGUBAH original!
sub[0] = 999
fmt.Println(original)  // [1 999 3 4 5] — berubah!
fmt.Println(sub)       // [999 3]

// Dan sebaliknya
original[2] = 777
fmt.Println(sub)       // [999 777] — sub juga berubah!

Kapan Ini Menjadi Bug #

// ANTI-PATTERN: fungsi yang memodifikasi slice dari luar tanpa disadari
func processFirst3(data []int) []int {
    result := data[:3]
    result[0] = 0    // MODIFIKASI data ASLI! Caller tidak mengharapkan ini
    return result
}

// BENAR: buat salinan independen
func processFirst3Safe(data []int) []int {
    if len(data) < 3 {
        return nil
    }
    result := make([]int, 3)
    copy(result, data[:3])   // copy membuat backing array baru
    result[0] = 0            // hanya memodifikasi result, bukan data asli
    return result
}

append — Cara Kerja dan Gotcha #

append menambahkan elemen ke slice dan mengembalikan slice baru:

s := []int{1, 2, 3}
s = append(s, 4)        // tambah satu elemen
s = append(s, 5, 6, 7)  // tambah beberapa elemen sekaligus

// Spread operator — gabungkan dua slice
a := []int{1, 2, 3}
b := []int{4, 5, 6}
c := append(a, b...)
fmt.Println(c)  // [1 2 3 4 5 6]

Kapan append Mengalokasikan Backing Array Baru #

Ini penting dipahami:

s := make([]int, 3, 5)  // len=3, cap=5
fmt.Printf("ptr=%p, len=%d, cap=%d\n", &s[0], len(s), cap(s))

s = append(s, 4)  // masih ada ruang (cap=5, len sekarang 4)
fmt.Printf("ptr=%p, len=%d, cap=%d\n", &s[0], len(s), cap(s))
// ptr SAMA — backing array tidak berubah!

s = append(s, 5)  // penuh! (cap=5, len sekarang 5)
s = append(s, 6)  // melebihi cap → alokasi backing array BARU (cap ≈ 2x)
fmt.Printf("ptr=%p, len=%d, cap=%d\n", &s[0], len(s), cap(s))
// ptr BERBEDA — backing array baru dengan cap ≈ 10

Gotcha: append Tidak Selalu Mempertahankan Sharing #

a := []int{1, 2, 3, 4, 5}
b := a[:3]  // b berbagi backing array dengan a
c := a[:3]  // c juga berbagi backing array yang sama

// Append ke b ketika masih ada cap
b = append(b, 99)   // cap masih cukup → memodifikasi backing array a!
fmt.Println(a)      // [1 2 3 99 5] — a berubah!
fmt.Println(b)      // [1 2 3 99]
fmt.Println(c)      // [1 2 3] — c masih [1 2 3] (len=3, tidak "melihat" elemen ke-4)

// Append ke b lagi setelah cap penuh
b = append(b, 88, 77, 66)  // melebihi cap → backing array BARU
b[0] = 0            // sekarang tidak mempengaruhi a
fmt.Println(a)      // [1 2 3 99 5] — a tidak berubah

Selalu simpan hasil append ke variabel. append mungkin mengembalikan slice dengan backing array yang berbeda dari input. Jika kamu tidak menyimpan hasilnya, semua penambahan hilang.

// ANTI-PATTERN: hasil append diabaikan
func addItem(s []int, item int) {
    append(s, item)  // ✗ hasil dibuang! tidak ada efek
}

// BENAR: kembalikan slice baru
func addItem(s []int, item int) []int {
    return append(s, item)  // ✓
}

copy — Membuat Slice Independen #

copy(dst, src) menyalin elemen dari src ke dst dan mengembalikan jumlah elemen yang disalin (minimum dari len(dst) dan len(src)):

src := []int{1, 2, 3, 4, 5}

// Salinan penuh
dst := make([]int, len(src))
n := copy(dst, src)
fmt.Println(dst, n)  // [1 2 3 4 5] 5

// Modifikasi dst tidak mempengaruhi src
dst[0] = 999
fmt.Println(src)  // [1 2 3 4 5] — tidak berubah!

// Salinan parsial — copy mengambil minimum dari len(dst) dan len(src)
partial := make([]int, 3)
copy(partial, src)       // hanya 3 elemen yang disalin
fmt.Println(partial)     // [1 2 3]

// Copy antar posisi dalam slice yang sama (overlap aman)
s := []int{1, 2, 3, 4, 5}
copy(s[1:], s[0:])       // geser semua elemen ke kanan satu posisi
fmt.Println(s)           // [1 1 2 3 4]

Operasi Idiomatik #

Delete Elemen #

s := []int{1, 2, 3, 4, 5}

// Hapus elemen di index i (tidak mempertahankan urutan — lebih cepat)
func deleteUnordered(s []int, i int) []int {
    s[i] = s[len(s)-1]  // pindahkan elemen terakhir ke posisi i
    return s[:len(s)-1]  // kurangi panjang
}

// Hapus elemen di index i (mempertahankan urutan)
func deleteOrdered(s []int, i int) []int {
    return append(s[:i], s[i+1:]...)
}

// Contoh
s = deleteOrdered(s, 2)
fmt.Println(s)  // [1 2 4 5]

Insert Elemen #

// Insert nilai v di index i
func insert(s []int, i int, v int) []int {
    s = append(s, 0)           // tambah ruang di akhir
    copy(s[i+1:], s[i:])       // geser elemen ke kanan
    s[i] = v                   // isi posisi i
    return s
}

s := []int{1, 2, 4, 5}
s = insert(s, 2, 3)  // insert 3 di index 2
fmt.Println(s)        // [1 2 3 4 5]

Filter — Ambil yang Memenuhi Kondisi #

// Filter in-place — reuse backing array, lebih efisien memori
func filter(s []int, keep func(int) bool) []int {
    result := s[:0]  // slice dengan len=0, cap=cap(s), backing array sama
    for _, v := range s {
        if keep(v) {
            result = append(result, v)
        }
    }
    return result
}

angka := []int{1, -2, 3, -4, 5, -6}
positif := filter(angka, func(n int) bool { return n > 0 })
fmt.Println(positif)  // [1 3 5]

Deduplicate — Hapus Duplikat #

func deduplicate(s []int) []int {
    if len(s) == 0 {
        return s
    }
    seen := make(map[int]bool)
    result := s[:0]
    for _, v := range s {
        if !seen[v] {
            seen[v] = true
            result = append(result, v)
        }
    }
    return result
}

data := []int{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}
fmt.Println(deduplicate(data))  // [3 1 4 5 9 2 6]

Reverse #

func reverse(s []int) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

s := []int{1, 2, 3, 4, 5}
reverse(s)
fmt.Println(s)  // [5 4 3 2 1]

Sorting Slice #

Package sort menyediakan fungsi untuk mengurutkan slice:

import "sort"

// Integer
angka := []int{3, 1, 4, 1, 5, 9, 2, 6}
sort.Ints(angka)
fmt.Println(angka)  // [1 1 2 3 4 5 6 9]

// String
kata := []string{"banana", "apple", "cherry", "date"}
sort.Strings(kata)
fmt.Println(kata)  // [apple banana cherry date]

// Float
nilai := []float64{3.14, 1.41, 2.71, 1.73}
sort.Float64s(nilai)
fmt.Println(nilai)  // [1.41 1.73 2.71 3.14]

// Custom — sort.Slice dengan fungsi less
type Person struct {
    Name string
    Age  int
}

orang := []Person{
    {"Charlie", 30},
    {"Alice", 25},
    {"Bob", 35},
}

// Sort by name
sort.Slice(orang, func(i, j int) bool {
    return orang[i].Name < orang[j].Name
})
fmt.Println(orang)  // [{Alice 25} {Bob 35} {Charlie 30}]

// Sort by age (descending)
sort.Slice(orang, func(i, j int) bool {
    return orang[i].Age > orang[j].Age  // > untuk descending
})
fmt.Println(orang)  // [{Bob 35} {Charlie 30} {Alice 25}]

// Cek apakah sudah terurut
fmt.Println(sort.IntsAreSorted([]int{1, 2, 3, 4}))  // true
fmt.Println(sort.IntsAreSorted([]int{1, 3, 2, 4}))  // false

// Binary search pada slice yang sudah terurut
sort.Ints(angka)
i, found := sort.Find(len(angka), func(i int) int {
    return angka[i] - 5  // cari 5
})
fmt.Println(i, found)  // index dan apakah ditemukan

Pre-alokasi untuk Performa #

Re-alokasi backing array saat append melebihi kapasitas adalah operasi mahal (alokasi memori baru + copy semua elemen). Jika kamu tahu berapa elemen yang akan ada, pre-alokasi dengan make menghindari re-alokasi berulang:

// ANTI-PATTERN: re-alokasi berulang
func buildSliceSlow(n int) []int {
    var result []int              // cap=0
    for i := 0; i < n; i++ {
        result = append(result, i)  // re-alokasi ~log(n) kali!
    }
    return result
}

// BENAR: pre-alokasi sekali
func buildSliceFast(n int) []int {
    result := make([]int, 0, n)   // cap=n dari awal
    for i := 0; i < n; i++ {
        result = append(result, i)  // tidak pernah re-alokasi
    }
    return result
}

// Atau jika panjang final sama dengan kapasitas:
func buildSliceDirect(n int) []int {
    result := make([]int, n)    // len=n, semua zero
    for i := range result {
        result[i] = i           // assign langsung, tidak butuh append
    }
    return result
}

Contoh Program Lengkap #

Program berikut membangun sistem inventaris sederhana yang menggunakan berbagai operasi slice:

package main

import (
    "fmt"
    "sort"
    "strings"
)

type Product struct {
    ID       int
    Name     string
    Category string
    Price    float64
    Stock    int
}

type Inventory struct {
    products []Product
    nextID   int
}

func NewInventory() *Inventory {
    return &Inventory{
        products: make([]Product, 0, 16), // pre-alokasi
    }
}

func (inv *Inventory) Add(name, category string, price float64, stock int) {
    inv.nextID++
    inv.products = append(inv.products, Product{
        ID:       inv.nextID,
        Name:     name,
        Category: category,
        Price:    price,
        Stock:    stock,
    })
}

// Filter produk berdasarkan kriteria
func (inv *Inventory) Filter(keep func(Product) bool) []Product {
    result := make([]Product, 0)
    for _, p := range inv.products {
        if keep(p) {
            result = append(result, p)
        }
    }
    return result
}

// Hapus produk berdasarkan ID
func (inv *Inventory) Remove(id int) bool {
    for i, p := range inv.products {
        if p.ID == id {
            // Hapus dengan mempertahankan urutan
            inv.products = append(inv.products[:i], inv.products[i+1:]...)
            return true
        }
    }
    return false
}

// Update stok
func (inv *Inventory) UpdateStock(id, delta int) error {
    for i := range inv.products {
        if inv.products[i].ID == id {
            newStock := inv.products[i].Stock + delta
            if newStock < 0 {
                return fmt.Errorf("stok tidak cukup: tersedia %d, dikurangi %d",
                    inv.products[i].Stock, -delta)
            }
            inv.products[i].Stock = newStock
            return nil
        }
    }
    return fmt.Errorf("produk ID %d tidak ditemukan", id)
}

// Ambil semua kategori unik
func (inv *Inventory) Categories() []string {
    seen := make(map[string]bool)
    var cats []string
    for _, p := range inv.products {
        if !seen[p.Category] {
            seen[p.Category] = true
            cats = append(cats, p.Category)
        }
    }
    sort.Strings(cats)
    return cats
}

// Sort produk berdasarkan field tertentu
func (inv *Inventory) SortBy(field string, ascending bool) {
    sort.Slice(inv.products, func(i, j int) bool {
        a, b := inv.products[i], inv.products[j]
        var less bool
        switch field {
        case "name":
            less = a.Name < b.Name
        case "price":
            less = a.Price < b.Price
        case "stock":
            less = a.Stock < b.Stock
        default:
            less = a.ID < b.ID
        }
        if ascending {
            return less
        }
        return !less
    })
}

// Laporan ringkasan
func (inv *Inventory) Summary() {
    if len(inv.products) == 0 {
        fmt.Println("Inventaris kosong")
        return
    }

    // Hitung statistik menggunakan slice operations
    totalValue := 0.0
    lowStock := inv.Filter(func(p Product) bool { return p.Stock < 5 })
    outOfStock := inv.Filter(func(p Product) bool { return p.Stock == 0 })

    for _, p := range inv.products {
        totalValue += p.Price * float64(p.Stock)
    }

    fmt.Printf("Total produk      : %d\n", len(inv.products))
    fmt.Printf("Total nilai stok  : Rp%.0f\n", totalValue)
    fmt.Printf("Stok menipis (<5) : %d produk\n", len(lowStock))
    fmt.Printf("Habis             : %d produk\n", len(outOfStock))
    fmt.Printf("Kategori          : %s\n", strings.Join(inv.Categories(), ", "))
}

// Cetak tabel produk
func printProducts(products []Product, title string) {
    if len(products) == 0 {
        fmt.Printf("\n%s: (kosong)\n", title)
        return
    }
    fmt.Printf("\n%s:\n", title)
    fmt.Printf("  %-4s %-20s %-12s %10s %6s\n",
        "ID", "Nama", "Kategori", "Harga", "Stok")
    fmt.Println("  " + strings.Repeat("-", 58))
    for _, p := range products {
        fmt.Printf("  %-4d %-20s %-12s %10.0f %6d\n",
            p.ID, p.Name, p.Category, p.Price, p.Stock)
    }
}

func main() {
    inv := NewInventory()

    // Tambah produk
    inv.Add("Laptop Pro 14",    "Elektronik", 15_000_000, 10)
    inv.Add("Mouse Wireless",   "Elektronik",    350_000,  3)
    inv.Add("Keyboard Mech",    "Elektronik",  1_500_000,  7)
    inv.Add("Monitor 27\"",     "Elektronik",  5_000_000,  2)
    inv.Add("Kaos Polos",       "Fashion",        85_000, 50)
    inv.Add("Celana Chino",     "Fashion",       250_000, 30)
    inv.Add("Jaket Bomber",     "Fashion",       450_000,  4)
    inv.Add("Buku Go Bahasa",   "Buku",          180_000, 15)
    inv.Add("Buku Clean Code",  "Buku",          220_000,  0)

    // Tampilkan semua produk
    printProducts(inv.products, "Semua Produk (urutan tambah)")

    // Sort berdasarkan harga — ascending
    inv.SortBy("price", true)
    printProducts(inv.products, "Urut Harga (murah ke mahal)")

    // Filter — hanya elektronik
    elektronik := inv.Filter(func(p Product) bool {
        return p.Category == "Elektronik"
    })
    printProducts(elektronik, "Produk Elektronik")

    // Filter — stok menipis
    menipis := inv.Filter(func(p Product) bool {
        return p.Stock > 0 && p.Stock < 5
    })
    printProducts(menipis, "Stok Menipis (1-4 unit)")

    // Slice operations
    fmt.Println("\n=== Operasi Slice ===")

    // Ambil 3 produk termahal — sort descending dulu
    inv.SortBy("price", false)
    top3 := inv.products[:3]  // slicing — berbagi backing array!
    fmt.Println("3 Produk Termahal:")
    for i, p := range top3 {
        fmt.Printf("  %d. %s — Rp%.0f\n", i+1, p.Name, p.Price)
    }

    // Buat salinan independen untuk modifikasi aman
    top3Copy := make([]Product, len(top3))
    copy(top3Copy, top3)
    top3Copy[0].Price = 0  // hanya mengubah salinan, bukan inv.products!
    fmt.Printf("Harga asli setelah modifikasi salinan: Rp%.0f\n",
        inv.products[0].Price)  // tidak berubah

    // Update stok
    fmt.Println("\n=== Update Stok ===")
    if err := inv.UpdateStock(1, -3); err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Berhasil kurangi stok Laptop Pro 14")
    }

    // Coba kurangi stok lebih dari yang ada
    if err := inv.UpdateStock(2, -10); err != nil {
        fmt.Println("Error:", err)
    }

    // Hapus produk
    removed := inv.Remove(9)  // hapus "Buku Clean Code" yang stok 0
    fmt.Printf("\nHapus produk ID 9: %v\n", removed)

    // Ringkasan akhir
    fmt.Println("\n=== Ringkasan Inventaris ===")
    inv.Summary()
}

Ringkasan #

  • Slice header tiga komponen: pointer, len, cap — memahami ini adalah kunci memahami semua perilaku slice.
  • Nil slice vs empty slice: var s []T (nil, JSON null) vs s := []T{} (empty, JSON []) — berbeda untuk serialisasi.
  • Slicing berbagi backing array — modifikasi sub-slice mempengaruhi slice asli; gunakan copy untuk slice independen.
  • append selalu reassign: s = append(s, v) — jangan append(s, v) tanpa menyimpan hasilnya.
  • append bisa alokasi backing array baru — saat melebihi cap; setelah itu sub-slice lama tidak lagi berbagi memori.
  • copy(dst, src) menyalin min(len(dst), len(src)) elemen — selalu buat dst dengan make yang cukup besar.
  • Pre-alokasi dengan make([]T, 0, n) ketika jumlah elemen bisa diperkirakan — hindari re-alokasi berulang.
  • sort.Slice dengan fungsi less kustom untuk sorting struct berdasarkan field apapun.
  • Filter in-place dengan result := s[:0] — reuse backing array tanpa alokasi baru.
  • Three-index slice s[low:high:max] untuk mengontrol cap slice hasil dan mencegah append tidak sengaja memodifikasi elemen di luar rentang.

← Sebelumnya: Array   Berikutnya: Map →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact