Memfilter slice Go sering dimulai dengan loop kecil yang mempertahankan elemen tertentu dan membuang sisanya. slices.DeleteFunc menyatakan operasi kebalikannya secara langsung: predicate menandai elemen yang akan dihapus, elemen yang dipertahankan tetap dalam urutan semula, dan backing array yang ada digunakan kembali.

Properti terakhir itu penting. DeleteFunc bukan filter yang menyalin data. Fungsi ini memutasi storage slice yang diberikan dan mengembalikan slice header dengan panjang hasil.

Predicate memilih elemen yang akan dihapus

Fungsi ini menerima slice dan deletion predicate:

func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S

Hasil predicate true menghapus elemen. Hasil false mempertahankannya.

package main

import (
    "fmt"
    "slices"
)

func main() {
    values := []int{2, 7, 4, 9, 6}

    values = slices.DeleteFunc(values, func(v int) bool {
        return v%2 != 0
    })

    fmt.Println(values)
}

Hasilnya:

[2 4 6]

Nilai yang dipertahankan menjaga urutan relatifnya. Karena itu, operasi ini cocok ketika urutan slice memiliki arti, berbeda dari teknik penghapusan yang mengganti elemen yang dihapus dengan elemen terakhir.

Slice yang dikembalikan harus menggantikan header lama

Menghapus elemen mengubah panjang slice, sehingga nilai yang dikembalikan merupakan bagian dari kontrak operasi.

items = slices.DeleteFunc(items, expired)

Memanggil DeleteFunc lalu mengabaikan hasilnya membuat slice header lama tidak berubah meskipun backing array telah dimodifikasi. Kode yang memakai header lama itu dapat melihat elemen yang sudah dipindahkan dan zero value setelah akhir logis yang baru.

Pertimbangan yang sama berlaku untuk alias. Jika dua nilai slice merujuk ke backing array yang sama, mutasi melalui DeleteFunc terlihat melalui keduanya pada rentang yang saling tumpang tindih.

base := []string{"keep", "drop", "stay"}
alias := base

base = slices.DeleteFunc(base, func(s string) bool {
    return s == "drop"
})

fmt.Println(base)  // [keep stay]
fmt.Println(alias) // [keep stay ]

String kosong terakhir pada alias bukan elemen tambahan yang dipertahankan. Itu adalah tail yang sudah di-zero-kan tetapi masih terlihat melalui slice header lama yang panjangnya tiga elemen.

Slot tail yang dihapus dibersihkan

Rilis Go saat ini menetapkan bahwa DeleteFunc men-zero-kan elemen antara panjang baru dan panjang awal. Untuk tipe elemen yang memuat pointer, membersihkan slot usang tersebut menghapus referensi yang jika tidak dibersihkan dapat membuat object tetap reachable melalui backing array.

type Record struct {
    ID int
}

records := []*Record{
    {ID: 10},
    {ID: 20},
    {ID: 30},
}

records = slices.DeleteFunc(records, func(r *Record) bool {
    return r.ID == 20
})

Setelah pemanggilan, slice logis berisi record dengan ID 10 dan 30. Slot usang pada tail diset menjadi nil.

Perilaku pembersihan tail ini menjadi bagian dari kontrak standard library sejak Go 1.22. Kode yang ditulis berdasarkan perilaku lama tidak boleh bergantung pada nilai usang yang tetap berada di luar panjang hasil.

Nilness dipertahankan untuk hasil kosong

Jika semua elemen dihapus, slice hasil memiliki nilness yang sama dengan input.

var nilSlice []int
nilSlice = slices.DeleteFunc(nilSlice, func(int) bool { return true })
fmt.Println(nilSlice == nil) // true

empty := []int{}
empty = slices.DeleteFunc(empty, func(int) bool { return true })
fmt.Println(empty == nil) // false

Input non-nil yang menjadi kosong tetap non-nil. Detail ini dapat penting pada batas serialization atau API yang membedakan null slice dari empty slice.

Filtering in-place memiliki konsekuensi ownership

DeleteFunc cocok untuk kode yang memiliki storage slice tersebut atau memang mengizinkan mutasi. Fungsi ini menghindari pembuatan result slice terpisah hanya untuk filtering, tetapi konsekuensinya caller harus memperlakukan isi slice awal sebagai data yang telah dimodifikasi.

Ketika input harus tetap utuh, destination terpisah menyatakan kontrak ownership yang berbeda. Salah satu bentuk umum adalah menambahkan nilai yang dipertahankan ke storage baru:

filtered := make([]Item, 0, len(items))
for _, item := range items {
    if keep(item) {
        filtered = append(filtered, item)
    }
}

Bentuk tersebut mengalokasikan outer slice storage yang independen. DeleteFunc sebaliknya memadatkan elemen yang dipertahankan ke backing array yang diberikan.

Perbedaannya lebih penting daripada sekadar sintaks. Pilihan di antara kedua bentuk menentukan apakah alias dapat mengamati mutasi dan apakah data urutan awal tetap tersedia setelah filtering.

Side effect pada predicate perlu diperhatikan

Deletion predicate sebaiknya tetap berfokus pada klasifikasi. Predicate menerima setiap nilai elemen, bukan index, dan operasi dapat menata ulang elemen yang dipertahankan di backing array yang sama saat memadatkan hasil.

Predicate yang memutasi shared state yang dirujuk oleh elemen slice dapat membuat kondisi filtering lebih sulit dipahami. Untuk elemen yang membawa reference, nilai yang diberikan ke predicate tetap dapat merujuk ke object mutable meskipun elemen slice itu sendiri diberikan by value.

Batas penggunaan slices.DeleteFunc karena itu cukup spesifik: operasi penghapusan berurutan dan in-place yang dikendalikan oleh element predicate. Ketika ownership storage sesuai dengan kontrak tersebut, fungsi ini menggantikan kode compaction manual sekaligus menangani reference pada tail yang sudah tidak digunakan secara eksplisit.