range ist die Form von for, die eine Sammlung durchläuft. Sie liefert dir pro Durchlauf ein oder zwei Werte, und welche das sind, hängt vom Typ ab, über den du läufst:
| Operand | Erster Wert | Zweiter Wert | Hinweise |
|---|---|---|---|
Slice []T, Array [n]T | Index int | Element T, eine Kopie | |
Pointer auf Array *[n]T | Index | Element | |
| String | Byte-Index int | rune | dekodiert UTF-8 |
Map map[K]V | Schlüssel K | Wert V | zufällige Reihenfolge |
Channel chan T | Element T | keiner | bis der Channel geschlossen ist |
Ganzzahl n | 0 bis n minus 1 | keiner | Go 1.22 |
func(yield func() bool) | keiner | keiner | Go 1.23 |
func(yield func(V) bool) | V | keiner | Go 1.23 |
func(yield func(K, V) bool) | K | V | Go 1.23 |
Welche Variablen du bekommst
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
Eine Variable zu deklarieren, die du nicht benutzt, ist ein Compilerfehler, also lass die weg oder ersetz sie durch _, die du nicht brauchst. for range 3 {} ist der kürzeste Weg, etwas dreimal zu wiederholen.
Der Wert ist eine Kopie
In jedem Durchlauf wird das Element in die Wertvariable kopiert. Die Variable zu ändern ändert die Sammlung nicht:
Das Kopieren kostet außerdem Zeit, wenn die Elemente große Structs sind. Bei einem Slice großer Structs vermeidest du die Kopie mit for i := range s und s[i].
Strings liefern Runes
range über einen String dekodiert UTF-8. Der erste Wert ist der Byte-Offset, an dem jedes Zeichen beginnt, also kann er Zahlen überspringen:
Um Bytes statt Zeichen zu durchlaufen, nimm eine Indexschleife, for i := 0; i < len(s); i++, oder range über []byte(s). Die Details zu UTF-8 stehen auf der Seite zu Runes und Bytes.
Maps: zufällige Reihenfolge
Die Iterationsreihenfolge von Maps ist nicht festgelegt, und die Runtime randomisiert sie, damit Code nicht von einer bestimmten Reihenfolge abhängig werden kann. Für eine deterministische Ausgabe sortierst du die Schlüssel:
Einträge beim Durchlaufen zu löschen ist sicher, und ein gelöschter Eintrag, der noch nicht erreicht wurde, wird nicht geliefert. Einträge hinzuzufügen ist erlaubt, aber unvorhersehbar: Ein neuer Schlüssel taucht später in derselben Schleife vielleicht auf, vielleicht nicht. fmt.Println gibt eine Map mit sortierten Schlüsseln aus, deshalb ist die letzte Zeile stabil.
Channels: bis sie geschlossen sind
range über einen Channel empfängt Werte, bis der Channel geschlossen und geleert ist. Schließt ihn niemand, blockiert die Schleife für immer (und wenn jede Goroutine blockiert, stoppt die Runtime mit fatal error: all goroutines are asleep - deadlock!):
Der Sender schließt den Channel, nie der Empfänger. Gepufferte Channels und select behandelt die Seite zu Channels.
Ganzzahlen (Go 1.22)
range n zählt von 0 bis ausschließlich n. Die Schleifenvariable hat den Typ von 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
Auch ein negatives n führt zu null Durchläufen. n wird einmal ausgewertet, bevor die Schleife beginnt.
Was range auswertet und wann
Der Ausdruck nach range wird einmal ausgewertet, vor dem ersten Durchlauf. Bei einem Slice nutzt range danach diesen Slice-Header, also steht seine Länge fest:
Die angehängte 99 wird nie besucht, weil die Länge der Schleife auf 3 festgelegt war. Aber nums[2] = 30 wird gesehen, weil nums zu diesem Zeitpunkt noch das Backing-Array mit dem durchlaufenen Slice teilt. Vertausch die beiden Zeilen, und die 30 verschwindet auch: Das append überschreitet die Kapazität von 3, legt ein neues Array an, und der Schreibzugriff landet dort. range über einen Array-Wert kopiert zuerst das ganze Array, also wird die Änderung an arr[2] nicht gesehen; mit range über &arr oder arr[:] vermeidest du die Kopie.
range über Funktionen (Go 1.23)
Seit Go 1.23 akzeptiert range auch Iterator-Funktionen. Ein Iterator nimmt einen Callback yield, ruft ihn einmal pro Wert auf und hört auf, wenn yield false zurückgibt (das passiert, wenn der Schleifenrumpf break oder return ausführt):
iter.Seq[V] und iter.Seq2[K, V] sind die Standardnamen für die beiden Iterator-Formen. Die Standardbibliothek gibt sie aus slices.All, slices.Values, slices.Backward, maps.Keys, maps.Values, maps.All zurück und in Go 1.24 aus strings.SplitSeq, strings.Lines und bytes.SplitSeq. Der Iterator muss den Rückgabewert von yield beachten: yield erneut aufzurufen, nachdem es false zurückgegeben hat, löst eine Panic aus.
Schleifenvariablen gelten pro Durchlauf (Go 1.22)
Seit Go 1.22 sind die von for ... range deklarierten Variablen in jedem Durchlauf neu. Fängst du v in einer Closure oder Goroutine ein, fängst du den Wert dieses Durchlaufs ein, keine geteilte Variable, die am Ende das letzte Element enthält. Vor Go 1.22 brauchtest du v := v in der Schleife; diese Zeile ist jetzt überflüssig. Die Regel richtet sich nach der go-Version in go.mod, also behält ein altes Modul das alte Verhalten.
Häufige Fehler
- Die Adresse der Wertvariable nehmen. Vor Go 1.22 speicherte
ptrs = append(ptrs, &v)jedes Mal dieselbe Adresse. Seit 1.22 speichert es verschiedene Adressen, aber jede zeigt auf eine Kopie, nicht auf das Element im Slice. Nimm&items[i], um in den Slice zu zeigen. - Eine Reihenfolge bei Maps erwarten. Eine Ausgabe, die in einem kleinen Test sortiert aussieht, bleibt nicht sortiert.
- range über einen Channel, den niemand schließt. Die Schleife und die Goroutine, in der sie läuft, warten ewig.
- range über ein großes Array als Wert. Zuerst wird das ganze Array kopiert. Durchlaufe stattdessen einen Slice davon.
Häufig gestellte Fragen
Was liefert range in Go?
Das hängt vom Operanden ab. Bei einem Slice oder Array: Index und Element. Bei einem String: Byte-Index und Rune. Bei einer Map: Schlüssel und Wert. Bei einem Channel: jeden empfangenen Wert. Bei einer Ganzzahl n (Go 1.22): 0 bis n minus 1. Bei einer Iterator-Funktion (Go 1.23): was die Funktion liefert. Du kannst die zweite Variable weglassen oder die erste mit _ verwerfen.
Warum ändert das Ändern des range-Werts meinen Slice nicht?
Die Wertvariable ist eine Kopie des Elements. for _, v := range items { v.Price = 0 } ändert nur die Kopie. Nimm stattdessen den Index: for i := range items { items[i].Price = 0 }, oder durchlaufe einen Slice von Pointern.
Kann ich in Go mit range über eine Ganzzahl laufen?
Ja, seit Go 1.22: for i := range 5 { ... } läuft mit i von 0 bis 4. for range 5 { ... } wiederholt fünfmal ohne Variable. Die go.mod des Moduls muss go 1.22 oder höher angeben.
Ist es sicher, beim Durchlaufen einer Map daraus zu löschen?
Ja. Löschst du einen Eintrag, der noch nicht erreicht wurde, wird er nicht mehr geliefert, und den aktuellen Eintrag zu löschen ist in Ordnung. Auch das Hinzufügen von Einträgen während der Schleife ist erlaubt, aber ein neuer Eintrag wird vielleicht besucht, vielleicht nicht, also verlass dich nicht darauf.