Go'da tek döngü anahtar sözcüğü for'dur ve üç biçimi vardır: C'deki gibi for i := 0; i < n; i++, başka dillerdeki while yerine geçen for koşul ve sonsuz döngü için yalın for. Dizi, slice, string, map, kanal, tam sayı ve iterator fonksiyonları üzerinde dolaşmak için for ... range kullanılır. Go 1.22'den beri her döngü turu kendi değişkenini alır ve for i := range 10 biçimiyle tam sayı üzerinde dolaşılabilir.
Bu yazıda for'un üç biçimini, range'in her veri tipindeki davranışını, etiketli break ve continue kullanımını, Go 1.22 döngü değişkeni değişikliğini (aynı programı eski ve yeni dil sürümüyle çalıştırarak) ve döngülerde en sık yapılan hataları spesifikasyona dayanarak anlatıyoruz. Örnekler Go 1.27.3 ile çalıştırıldı. Serinin haritası için Go öğrenme rehberi sayfasına bakabilirsiniz.
for döngüsünün üç biçimi
Effective Go'nun ifadesiyle Go'daki for, C'deki for ile while'ı birleştirir ve do-while yoktur. Spesifikasyona göre üç parçalı biçimdeki başlangıç deyimi ilk koşul kontrolünden önce bir kez çalışır; son deyim (post) ise her gövde çalışmasından sonra, yalnızca gövde çalıştıysa çalışır. Başlangıç deyimi kısa değişken bildirimi olabilir, son deyim olamaz. Parçalardan herhangi biri boş bırakılabilir ama yalnızca koşul yoksa noktalı virgüller zorunludur. Koşul hiç yazılmazsa true yazılmış sayılır.
package main
import "fmt"
func main() {
// 1) Klasik üç parçalı for
toplam := 0
for i := 1; i <= 10; i++ {
toplam += i
}
fmt.Println(toplam) // 55
// 2) Yalnızca koşul: başka dillerdeki while
n := 1
for n < 100 {
n *= 2
}
fmt.Println(n) // 128
// 3) Koşulsuz: sonsuz döngü, break ile çıkılır
deneme := 0
for {
deneme++
if deneme == 3 {
break
}
}
fmt.Println(deneme) // 3
// Paralel atama ile iki sayaç
a := []int{1, 2, 3, 4, 5}
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
fmt.Println(a) // [5 4 3 2 1]
}Son örnekteki dizi ters çevirme, Effective Go'daki bir ayrıntıyı gösterir: Go'da virgül operatörü yoktur ve ++ ile -- ifade değil deyimdir. Bu yüzden döngüde birden çok değişkeni birlikte ilerletmek için paralel atama (i, j = i+1, j-1) kullanılır.
range ile dolaşma
![range ile dolaşma: array, dizi işaretçisi, slice, string, map[K]V, kanal](/assets/blog/go-for-dongusu-ve-range-b2.webp)
range yan tümcesi bir koleksiyonun her elemanı için döngü gövdesini bir kez çalıştırır ve her turda bir ya da iki yineleme değeri üretir. Hangi değerlerin üretildiği, range ifadesinin tipine bağlıdır. Spesifikasyondaki tablo özetle şöyledir:
| Range ifadesi | 1. değer | 2. değer |
|---|---|---|
| array, dizi işaretçisi, slice | indis (int) | eleman |
| string | baytın indisi (int) | rune (kod noktası) |
| map[K]V | anahtar (K) | değer (V) |
| kanal | kanaldan alınan eleman | yok |
| tam sayı n (Go 1.22) | 0 ile n-1 arası değer | yok |
| iterator fonksiyonu (Go 1.23) | yield argümanı | yield'in ikinci argümanı |
İkinci değere ihtiyacınız yoksa yazmazsınız; yalnızca ikinci değere ihtiyacınız varsa birincinin yerine boş tanımlayıcı _ koyarsınız. Aşağıdaki örnek altı farklı range kullanımını bir arada gösteriyor:
package main
import (
"fmt"
"maps"
"slices"
)
func main() {
diller := []string{"Go", "Rust", "Zig"}
for i, d := range diller {
fmt.Println(i, d)
}
for i := range diller { // yalnızca indis
fmt.Print(i, " ")
}
fmt.Println()
stok := map[string]int{"elma": 5, "armut": 2, "kiraz": 9}
for _, k := range slices.Sorted(maps.Keys(stok)) {
fmt.Println(k, stok[k])
}
for i, r := range "çağ" {
fmt.Printf("%d:%c ", i, r)
}
fmt.Println()
for i := range 3 {
fmt.Print(i, " ")
}
fmt.Println()
ch := make(chan int, 3)
ch <- 10
ch <- 20
close(ch)
for v := range ch {
fmt.Print(v, " ")
}
fmt.Println()
// Çıktı:
// 0 Go
// 1 Rust
// 2 Zig
// 0 1 2
// armut 2
// elma 5
// kiraz 9
// 0:ç 2:a 3:ğ
// 0 1 2
// 10 20
}Örnekteki her bloğun açıklanması gereken bir ayrıntısı var. Slice üzerinde indisler sıfırdan artan sırayla gelir; nil bir slice'ta döngü hiç dönmez. String üzerinde range bayt bayt değil Unicode kod noktası kod noktası ilerler: "çağ" metninde ç iki bayt tuttuğu için a'nın indisi 2'dir. Geçersiz bir UTF-8 dizisiyle karşılaşıldığında ikinci değer 0xFFFD (yerine geçme karakteri) olur ve döngü yalnızca bir bayt ilerler. String'lerin bayt ve rune yapısı string, rune, byte ve UTF-8 yazısında ayrıntılı anlatılıyor.
Map üzerindeki dolaşma sırası tanımlı değildir ve spesifikasyona göre bir dolaşmadan diğerine aynı kalacağı garanti edilmez. Bu yüzden örnekte anahtarlar önce maps.Keys ile bir iterator olarak alındı, slices.Sorted ile sıralı bir slice'a toplandı ve öyle dolaşıldı; çıktının her çalıştırmada aynı olmasını sağlayan budur. Dolaşma sırasında henüz ulaşılmamış bir eleman silinirse o eleman üretilmez; yeni eklenen bir elemanın üretilip üretilmeyeceği ise belirsizdir. Kanal üzerinde range, kanal kapatılana kadar gelen değerleri alır; nil bir kanalda sonsuza kadar bekler. Kanalların ayrıntısı channel (kanallar) yazısında.
Tam sayı üzerinde range (Go 1.22)
Go 1.22 sürüm notlarına göre for döngüleri artık tam sayılar üzerinde range yapabilir. for i := range n, 0'dan n-1'e kadar değerleri artan sırayla üretir; n sıfır ya da negatifse döngü hiç çalışmaz. Değişkene ihtiyacınız yoksa for range n yazmanız yeterlidir. Yineleme değerinin tipi kurallara bağlıdır: n tipli bir tam sayıysa değerler de o tipte olur; tipsiz bir sabitse ve değişken range ile tanımlanıyorsa varsayılan tip olan int kullanılır.
package main
import "fmt"
func main() {
var n uint8 = 3
for i := range n {
fmt.Printf("%d:%T ", i, i)
}
fmt.Println()
for range 2 {
fmt.Print("tekrar ")
}
fmt.Println()
for i := range -5 {
fmt.Println("hiç çalışmaz", i)
}
// Çıktı:
// 0:uint8 1:uint8 2:uint8
// tekrar tekrar
}Önceden var olan bir değişkene range ile atama yapıldığında değerlerin tipi o değişkenin tipi olur ve sabitin bu tipe sığması gerekir. Spesifikasyondaki örnekte olduğu gibi uint8 bir değişkene range 256 atanamaz:
package main
import "fmt"
func main() {
var u uint8
for u = range 256 {
}
fmt.Println(u)
}# command-line-arguments
./main.go:7:16: cannot use 256 (untyped int constant) as uint8 value in range clause (overflows)break, continue ve etiketler
break en içteki for, switch ya da select deyimini sonlandırır; continue ise en içteki for döngüsünün bir sonraki turuna geçer. İç içe döngülerde dıştaki döngüyü etkilemek için döngüye bir etiket verip break Etiket ya da continue Etiket yazarsınız. Spesifikasyona göre etiketler blok kapsamlı değildir, etiket olmayan tanımlayıcılarla çakışmaz ve kapsamları tanımlandıkları fonksiyonun gövdesidir. Hiç kullanılmayan bir etiket tanımlamak ise hatadır.
package main
import "fmt"
func main() {
matris := [][]int{{1, 2, 3}, {4, -1, 6}, {7, 8, 9}}
Satir:
for y, satir := range matris {
for x, v := range satir {
if v < 0 {
fmt.Println("negatif değer:", y, x)
continue Satir
}
if v == 8 {
fmt.Println("8 bulundu, çıkılıyor")
break Satir
}
}
fmt.Println("satır tamam:", y)
}
// Çıktı:
// satır tamam: 0
// negatif değer: 1 1
// 8 bulundu, çıkılıyor
}Örnekte negatif bir değer bulunduğunda continue Satir satırın geri kalanını atlayıp bir sonraki satıra geçti; bu yüzden 1 numaralı satır için "satır tamam" yazılmadı. 8 bulunduğunda break Satir iki döngüden birden çıktı. Go'da goto da vardır, ama spesifikasyon iki kısıt koyar: atlama, atlanan noktada kapsama girmemiş bir değişkeni kapsama sokamaz ve bir bloğun dışından o bloğun içindeki etikete atlanamaz. Günlük kodda etiketli break ve continue neredeyse her ihtiyacı karşılar.
Go 1.22: her turda yeni döngü değişkeni

Go 1.22 sürüm notlarına göre bu sürümden önce for döngüsünün tanımladığı değişkenler bir kez oluşturulur ve her turda güncellenirdi; Go 1.22'de her tur yeni değişkenler oluşturur. Spesifikasyon bunu şöyle tanımlar: ilk turun değişkeni başlangıç deyimiyle tanımlanır, sonraki her turun değişkeni ise son deyim çalışmadan önce örtük olarak tanımlanır ve önceki turun o anki değeriyle başlatılır. Aynı kural range ile tanımlanan değişkenler için de geçerlidir.
Bu değişiklik, Go'nun en sık yapılan hatalarından birini ortadan kaldırdı. Go Blog'daki yazının anlattığı sorun, bir döngü değişkenine o turun ötesinde referans tutulmasıdır: closure ya da goroutine değişkeni yakalar, değişken sonraki turlarda değişir ve beklenen değer yerine son değer görülür. Blog bu hatanın birçok şirkette üretim sorunlarına yol açtığını belirtir. Eski davranışı ve yenisini aynı programla görelim:
package main
import "fmt"
func main() {
var yazicilar []func()
for i := 0; i < 3; i++ {
yazicilar = append(yazicilar, func() { fmt.Print(i, " ") })
}
for _, y := range yazicilar {
y()
}
fmt.Println()
}Bu programı iki ayrı modülde çalıştırdık. Tek fark go.mod dosyasındaki go satırıydı. Blog yazısının açıkladığı gibi, geriye dönük uyumluluk için yeni kural yalnızca go.mod dosyasında go 1.22 veya daha yenisini bildiren modüllerdeki paketlere uygulanır. Go 1.27.3 araç zinciri, go 1.21 bildiren modülde eski anlamı korudu:
| go.mod satırı | Çıktı | Açıklama |
|---|---|---|
| go 1.27 | 0 1 2 | Her tur kendi i değişkenini yakaladı |
| go 1.21 | 3 3 3 | Tüm closure'lar aynı i değişkenini paylaştı; döngü bittiğinde değeri 3 |
Eski kodlarda gördüğünüz v := v ya da değişkeni closure'a argüman olarak geçirme kalıpları bu sorunun çaresiydi. FAQ'daki closure ve goroutine maddesi de bu davranışın geriye dönüp bakıldığında bir hata olarak değerlendirildiğini ve Go 1.22 ile giderildiğini yazar. Modülünüz Go 1.22 veya daha yenisini bildiriyorsa bu kopyalara gerek kalmaz. Sürüm notlarına göre go vet de Go 1.22 veya daha yenisini gerektiren dosyalarda bu tür referansları artık raporlamaz. Eski bir projeyi güncellerken go satırını yükseltmenin döngü anlamını değiştirebileceğini unutmayın.
range ve iterator fonksiyonları

Go 1.23'ten beri range ifadesi, belirli imzaya sahip bir fonksiyon da olabilir: func(yield func(V) bool) gibi. Döngü her turda fonksiyonun yield'e verdiği değerleri alır; döngü break ile biterse yield false döner. Bu özellik, kendi veri yapılarınız için range desteği yazmanızı sağlar ve maps.Keys gibi standart kütüphane fonksiyonları da bunu kullanır. Konu, iter.Seq tipleriyle birlikte iterator ve range over func yazısında ayrıntılı ele alınıyor.
Mini örnek: kelime sıklığı
Range'in farklı biçimlerini bir arada kullanan küçük bir örnek: bir metindeki kelimelerin kaç kez geçtiğini sayıp alfabetik sırayla yazdıralım. strings.Fields metni boşluklardan bölüp bir string slice'ı döndürüyor; ilk döngü bu slice üzerinde dolaşıp map'teki sayacı arttırıyor. Map'te olmayan bir anahtarı okumak değer tipinin sıfır değerini verdiği için sayac[k]++ ilk görüşte de doğru çalışıyor. İkinci döngü, map sırası tanımsız olduğu için anahtarları önce sıralıyor.
package main
import (
"fmt"
"maps"
"slices"
"strings"
)
func main() {
metin := "go hızlı derlenir go basit go eğlenceli"
sayac := map[string]int{}
for _, k := range strings.Fields(metin) {
sayac[k]++
}
for _, k := range slices.Sorted(maps.Keys(sayac)) {
fmt.Printf("%-10s %d\n", k, sayac[k])
}
// Çıktı:
// basit 1
// derlenir 1
// eğlenceli 1
// go 3
// hızlı 1
}%-10s biçimi kelimeyi on karakterlik bir alana sola dayalı yazıyor; fmt paketi genişliği bayt değil rune cinsinden ölçtüğü için Türkçe karakter içeren "eğlenceli" kelimesi de doğru hizalandı. Bu örnek, büyük-küçük harf farkını ve noktalama işaretlerini dikkate almıyor; gerçek bir metin işleme aracında kelimeleri önce normalleştirmeniz gerekir. strings paketinin diğer işlevleri string ve UTF-8 yazısında.
Sık yapılan hatalar
1. range değişkenini değiştirerek slice'ı güncellemeye çalışmak. Range'in ikinci değeri elemanın bir kopyasıdır. Bu kopyayı değiştirmek slice'ı değiştirmez; elemanı güncellemek için indis üzerinden yazmanız gerekir. Aynı örnek ikinci bir tuzağı da gösteriyor: spesifikasyona göre range ifadesi döngü başlamadan önce bir kez değerlendirilir. Döngü içinde slice'a eleman eklemek, dolaşılan eleman sayısını değiştirmez:
package main
import "fmt"
func main() {
fiyatlar := []int{100, 200, 300}
for _, f := range fiyatlar {
f *= 2 // yalnızca kopyayı değiştirir
}
fmt.Println(fiyatlar) // [100 200 300]
for i := range fiyatlar {
fiyatlar[i] *= 2
}
fmt.Println(fiyatlar) // [200 400 600]
s := []int{1, 2, 3}
for i, v := range s {
if i == 0 {
s = append(s, 99) // range ifadesi döngü başında bir kez değerlendirildi
}
fmt.Print(v, " ")
}
fmt.Println(len(s)) // 1 2 3 4
}2. Map dolaşma sırasına güvenmek. Sıra tanımsızdır ve çalıştırmadan çalıştırmaya değişebilir. Test çıktıları, raporlar ya da karşılaştırmalar için anahtarları önce sıralayın. Sıralama yardımcıları slices, maps ve sort paketleri yazısında.
3. Kullanılmayan etiket bırakmak. Etiket tanımlayıp hiçbir break, continue ya da goto ile kullanmamak derleme hatasıdır:
package main
import "fmt"
func main() {
Dis:
for i := range 3 {
fmt.Println(i)
}
}# command-line-arguments
./main.go:6:1: label Dis defined and not used4. Eski modüllerde döngü değişkeni paylaşımını unutmak. go.mod dosyası go 1.22'den eski bir sürüm bildiriyorsa closure ve goroutine'ler hâlâ aynı değişkeni paylaşır. Ya go satırını yükseltin ya da değişkeni açıkça kopyalayın.
5. Açılış süslü parantezini yeni satıra yazmak. Otomatik noktalı virgül kuralı nedeniyle for deyiminin açılış parantezi aynı satırda olmalıdır; aksi hâlde derleyici sözdizimi hatası verir.
Sık Sorulan Sorular
Go'da while döngüsü var mı?
Ayrı bir anahtar sözcük yoktur. for koşul { ... } biçimi while ile aynı işi yapar; koşulsuz for { ... } ise sonsuz döngüdür ve break ya da return ile sonlanır. do-while karşılığı da yoktur; gövdenin en az bir kez çalışması gerekiyorsa sonsuz döngünün sonuna koşullu break koyabilirsiniz.
range ile dolaşırken map'ten eleman silebilir miyim?
Evet. Spesifikasyon, dolaşma sırasında henüz ulaşılmamış bir elemanın silinmesi durumunda o elemanın üretilmeyeceğini tanımlar. Effective Go'daki örnek de süresi dolan anahtarları dolaşma sırasında delete ile siler.
for i := range 10 hangi Go sürümünü gerektirir?
Tam sayı üzerinde range Go 1.22 ile geldi. Modülünüzün go.mod dosyasındaki go satırı 1.22 veya daha yeni olmalıdır; daha eski bir dil sürümü bildiren kodda bu yazım kabul edilmez. Aynı döngüyü go 1.21 bildiren bir modülde derlediğimizde Go 1.27.3 şu hatayı verdi:
# ornek/r
./main.go:6:17: cannot range over 3 (untyped int constant): requires go1.22 or later (-lang was set to go1.21; check go.mod)Döngü değişkenini closure içinde kullanmak artık güvenli mi?
Modülünüz Go 1.22 veya daha yenisini bildiriyorsa her tur kendi değişkenine sahip olduğu için closure o turun değerini görür. Yine de goroutine'lerle paylaşılan başka değişkenler için senkronizasyon gerekir; bu konu goroutine ve sync paketi yazılarında.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Go öğrenme rehberi · Go if ve switch · Go iterator ve range over func · Go fonksiyonlar ve closure · Go map kullanımı · Go slice, append ve copy