unisbadri.com » Python Java Golang Typescript Kotlin Ruby Rust Dart PHP

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:

Slice Header:
  ┌─────────────┬─────┬─────┐
  │   Pointer   │ Len │ Cap │
  └─────────────┴─────┴─────┘
        │
        ▼
  Backing Array: [ e0 | e1 | e2 | e3 | e4 | e5 | e6 | e7 ]
                   ↑                    ↑
                 pointer             pointer + cap
                   |←── len ──→|
                   |←────── cap ────────→|
  • 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