Menu

range di Golang: Semua Bentuknya, Hasilnya, dan Jebakannya

Apa yang dihasilkan for range untuk slice, array, string, map, channel, integer, dan fungsi iterator, kenapa variabel nilainya adalah salinan, apa yang terjadi saat kamu mengubah slice atau map selama range, dan range over func di Go 1.23.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

range adalah bentuk for yang menelusuri sebuah koleksi. Ia memberimu satu atau dua nilai per iterasi, dan nilai apa yang diberikan bergantung pada tipe yang kamu telusuri:

OperandNilai pertamaNilai keduaCatatan
slice []T, array [n]Tindeks intelemen T, sebuah salinan
pointer ke array *[n]Tindekselemen
stringindeks byte intrunemen-decode UTF-8
map map[K]Vkey Kvalue Vurutan acak
channel chan Telemen Ttidak adasampai channel ditutup
integer n0 sampai n dikurangi 1tidak adaGo 1.22
func(yield func() bool)tidak adatidak adaGo 1.23
func(yield func(V) bool)Vtidak adaGo 1.23
func(yield func(K, V) bool)KVGo 1.23

Memilih variabel yang kamu dapat

for i, v := range s {}  // both
for i := range s {}     // index (or key) only
for _, v := range s {}  // value only
for range s {}          // neither: just repeat len(s) times

Mendeklarasikan variabel yang tidak dipakai adalah compile error, jadi hilangkan atau ganti dengan blank identifier variabel yang tidak kamu butuhkan. for range 3 {} adalah cara terpendek untuk mengulang sesuatu tiga kali.

Nilainya adalah salinan

Di setiap iterasi, elemen disalin ke variabel nilai. Mengubah variabel itu tidak mengubah koleksinya:

Menyalin juga memakan waktu saat elemennya berupa struct besar. Untuk slice berisi struct besar, for i := range s dengan s[i] menghindari penyalinan itu.

String menghasilkan rune

Melakukan range pada string akan men-decode UTF-8. Nilai pertamanya adalah offset byte tempat setiap karakter dimulai, jadi angkanya bisa melompat:

Untuk menelusuri byte alih-alih karakter, pakai loop dengan indeks, for i := 0; i < len(s); i++, atau lakukan range pada []byte(s). Lihat halaman rune dan byte untuk detail UTF-8.

Map: urutan acak

Urutan iterasi map tidak ditentukan, dan runtime mengacaknya supaya kode tidak bisa bergantung pada satu urutan. Untuk output yang deterministik, urutkan key-nya:

Menghapus entri selama range itu aman, dan entri terhapus yang belum dicapai tidak akan dihasilkan. Menambah entri diperbolehkan tapi tidak bisa diprediksi: key baru mungkin muncul belakangan di perulangan yang sama, mungkin juga tidak. fmt.Println pada map mencetak key secara terurut, itulah sebabnya baris terakhir selalu stabil.

Channel: sampai ditutup

Melakukan range pada channel menerima nilai sampai channel itu ditutup dan dikosongkan. Kalau tidak ada yang menutupnya, perulangan terblokir selamanya (dan kalau setiap goroutine terblokir, runtime berhenti dengan fatal error: all goroutines are asleep - deadlock!):

Yang menutup channel adalah pengirimnya, tidak pernah penerimanya. Lihat halaman channel untuk buffered channel dan select.

Integer (Go 1.22)

range n menghitung naik dari 0 dan berhenti tepat sebelum n. Tipe variabel loop-nya sama dengan tipe n:

for i := range 3 {}         // i is int: 0, 1, 2
for i := range uint8(3) {}  // i is uint8
for range 0 {}              // runs zero times

n yang negatif juga berjalan nol kali. n dievaluasi satu kali, sebelum perulangan dimulai.

Apa yang dievaluasi range, dan kapan

Ekspresi setelah range dievaluasi satu kali, sebelum iterasi pertama. Untuk slice, range lalu memakai header slice itu, jadi panjangnya tetap:

99 yang ditambahkan tidak pernah dikunjungi karena panjang perulangannya sudah ditetapkan 3. Tapi nums[2] = 30 terlihat, karena saat itu nums masih berbagi backing array dengan slice yang sedang ditelusuri. Tukar kedua baris itu dan 30 juga ikut hilang: append melebihi kapasitas 3, mengalokasikan array baru, dan penulisannya justru terjadi di sana. Melakukan range pada nilai array menyalin seluruh array lebih dulu, jadi perubahan pada arr[2] tidak terlihat; lakukan range pada &arr atau arr[:] untuk menghindari penyalinan.

Range pada fungsi (Go 1.23)

Sejak Go 1.23, range juga menerima fungsi iterator. Iterator menerima callback yield, memanggilnya sekali untuk setiap nilai, dan berhenti saat yield mengembalikan false (yang terjadi saat badan perulangan menjalankan break atau return):

iter.Seq[V] dan iter.Seq2[K, V] adalah nama standar untuk kedua bentuk iterator. Standard library mengembalikannya dari slices.All, slices.Values, slices.Backward, maps.Keys, maps.Values, maps.All, dan di Go 1.24 dari strings.SplitSeq, strings.Lines, dan bytes.SplitSeq. Iterator harus menghormati nilai kembalian yield: memanggil yield lagi setelah ia mengembalikan false akan memicu panic.

Variabel loop berbeda di setiap iterasi (Go 1.22)

Sejak Go 1.22, variabel yang dideklarasikan oleh for ... range selalu baru di setiap iterasi. Menangkap v di closure atau goroutine berarti menangkap nilai iterasi itu, bukan variabel bersama yang akhirnya berisi elemen terakhir. Sebelum Go 1.22, kamu butuh v := v di dalam perulangan; baris itu kini tidak diperlukan lagi. Aturan ini ditentukan oleh versi go di go.mod, jadi module lama tetap memakai perilaku lama.

Kesalahan umum

  • Mengambil alamat variabel nilai. Sebelum Go 1.22, ptrs = append(ptrs, &v) menyimpan alamat yang sama setiap kali. Sejak 1.22, alamat yang disimpan berbeda-beda, tapi masing-masing menunjuk ke salinan, bukan ke elemen slice. Pakai &items[i] untuk menunjuk ke dalam slice.
  • Mengharapkan urutan map. Output yang terlihat terurut di test kecil tidak akan tetap terurut.
  • Melakukan range pada channel yang tidak pernah ditutup. Perulangan itu, dan goroutine yang menjalankannya, menunggu selamanya.
  • Melakukan range pada array besar sebagai nilai. Seluruh array disalin lebih dulu. Lakukan range pada slice dari array itu sebagai gantinya.

Pertanyaan yang sering diajukan

Apa yang dikembalikan range di Go?

Tergantung operand-nya. Untuk slice atau array: indeks dan elemen. Untuk string: indeks byte dan rune. Untuk map: key dan value. Untuk channel: setiap nilai yang diterima. Untuk integer n (Go 1.22): 0 sampai n dikurangi 1. Untuk fungsi iterator (Go 1.23): apa pun yang di-yield oleh fungsi itu. Kamu boleh menghilangkan variabel kedua, atau membuang yang pertama dengan _.

Kenapa mengubah nilai range tidak mengubah slice saya?

Variabel nilainya adalah salinan elemen. for _, v := range items { v.Price = 0 } hanya mengubah salinannya. Pakai indeks sebagai gantinya: for i := range items { items[i].Price = 0 }, atau lakukan range pada slice berisi pointer.

Bisakah melakukan range pada integer di Go?

Bisa, sejak Go 1.22: for i := range 5 { ... } berjalan dengan i dari 0 sampai 4. for range 5 { ... } mengulang lima kali tanpa variabel. go.mod milik module-nya harus menulis go 1.22 atau lebih baru.

Apakah aman menghapus dari map selama range di atasnya?

Aman. Menghapus entri yang belum dicapai berarti entri itu tidak akan dihasilkan, dan menghapus entri saat ini tidak masalah. Menambah entri selama perulangan juga diperbolehkan, tapi entri baru mungkin dikunjungi atau mungkin tidak, jadi jangan mengandalkannya.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai