range în Go

Cuvinte cheie și controlul execuției Go 1.0 (range peste int: Go 1.22, peste funcții: Go 1.23)

Parcurge într-un for elementele unui slice, array, map, text, canal, număr sau iterator.

Sintaxă
for i, v := range slice { }   // poziție, valoare
for k, v := range m { }       // cheie, valoare
for i, r := range text { }    // poziție în octeți, rună
for v := range canal { }      // până la close
for i := range n { }          // 0 .. n-1
for v := range seq { }        // funcție iterator

Ce face range

range se folosește doar în for și dă, la fiecare pas, una sau două valori. Pentru slice-uri și array-uri: poziția și elementul. Pentru map-uri: cheia și valoarea. Pentru texte: poziția în octeți și litera (rune), decodată din UTF-8. Pentru canale: valoarea primită, până când canalul este închis.

Dacă nu ai nevoie de o valoare, o înlocuiești cu _: for _, v := range s. Dacă vrei doar poziția sau cheia, scrii o singură variabilă: for i := range s. Din Go 1.22, for i := range 5 numără de la 0 la 4, iar din Go 1.23 poți parcurge funcții iterator (iter.Seq), de exemplu maps.Keys(m).

Ordinea parcurgerii unui map este aleatorie și diferă de la o rulare la alta. Dacă ai nevoie de o ordine fixă, sortează cheile (slices.Sorted(maps.Keys(m))) și parcurge-le pe ele.

Ce returnează

Nu returnează nimic; dă valori variabilelor buclei la fiecare iterație.

Exemple

Exemplul 1: Slice, text și număr

La text, poziția sare cu 2 la „ș”, pentru că litera ocupă 2 octeți în UTF-8.

package main

import "fmt"

func main() {
    note := []int{9, 7, 10}
    suma := 0
    for _, n := range note {
        suma += n
    }
    fmt.Println("suma notelor:", suma)

    for i, litera := range "Iași" {
        fmt.Printf("poziția %d: %c\n", i, litera)
    }

    for i := range 3 {
        fmt.Print(i, " ")
    }
    fmt.Println()
}

Rezultat

suma notelor: 26
poziția 0: I
poziția 1: a
poziția 2: ș
poziția 4: i
0 1 2

Exemplul 2: Map parcurs în ordine sortată

slices.Sorted(maps.Keys(m)) dă cheile sortate, deci rezultatul este același la fiecare rulare.

package main

import (
    "fmt"
    "maps"
    "slices"
)

func main() {
    populatie := map[string]int{
        "Iași":      271692,
        "Cluj":      286598,
        "Constanța": 263688,
    }
    for _, oras := range slices.Sorted(maps.Keys(populatie)) {
        fmt.Printf("%-10s %d\n", oras, populatie[oras])
    }
}

Rezultat

Cluj       286598
Constanța  263688
Iași       271692

Exemplul 3: range peste un canal

Bucla primește valori până când canalul este închis cu close.

package main

import "fmt"

func main() {
    comenzi := make(chan string, 3)
    comenzi <- "pizza"
    comenzi <- "ciorbă"
    comenzi <- "papanași"
    close(comenzi)

    for c := range comenzi {
        fmt.Println("pregătesc:", c)
    }
    fmt.Println("gata, canalul este închis")
}

Rezultat

pregătesc: pizza
pregătesc: ciorbă
pregătesc: papanași
gata, canalul este închis

Greșeli frecvente

  • Bazarea pe ordinea dintr-un map: ea diferă la fiecare rulare. Sortează cheile înainte.
  • for x := range s crezând că x este valoarea: cu o singură variabilă primești poziția (la slice) sau cheia (la map).
  • range peste un canal care nu este închis niciodată: bucla așteaptă la nesfârșit, iar programul se oprește cu all goroutines are asleep - deadlock!.

Comenzi înrudite

  • for: Singura buclă din Go: forma clasică, forma „while”, bucla infinită și parcurgerea cu range.
  • for range peste funcții (iteratori): Din Go 1.23, for range parcurge funcții iterator: funcții care trimit pe rând valorile prin yield.
  • map și map-uri: Tabel cheie-valoare (dicționar): căutare rapidă după cheie, adăugare, ștergere și verificare cu v, ok.
  • string-uri, rune și byte: Textul în Go este UTF-8: len dă octeți, range dă rune (caractere), byte este un octet, rune un caracter.
  • chan și canale (channels): Canalele (chan) transmit valori între goroutine în siguranță și le sincronizează; se creează cu make.