Памятка по for и range в Go

Памятка по for и range в Go #

Ключевое слово range используется вместе с for для перебора. Ниже памятка о разных вариантах использования for и for...range.

range по числовому диапазону #

Начиная с Go 1.22, можно выполнять range по целым числам:

n := 10
for i := range n {
	// перебор i от 0 до n-1
}

В любой версии Go работает for без range, что позволяет, например, перебрать числа в обратном порядке:

n := 10
for i := n-1; i >= 0; i-- {
	// перебор от n-1 до 0
}

range по map #

В языке Go map — это хеш-таблица:

  • начиная с Go 1.24 используется открытая адресация и структура данных Swiss Table
  • до версии Go 1.23 (включительно) использовалась закрытая адресация

Особенность перебора map в том, что начальная позиция в хеш-таблице при каждом переборе может быть выбрана случайно — то есть порядок перебора ключей может быть разным даже в соседних циклах for.

data := map[string]float64 {
	"apple": 108.0,
	"banana": 120.0,
	"cherry": 500.0,
}
for key, value := range data {
	// key перебирает ключи
	// value перебирает соответствующие значения
}

Начиная с Go 1.23, можно перебирать только ключи либо только значения функциями пакета maps:

data := map[string]float64 {
	"apple": 108.0,
	"banana": 120.0,
	"cherry": 500.0,
}

for key := range maps.Keys(data) {
	// key перебирает ключи
}

for value := range maps.Values(data) {
	// value перебирает значения
}

range по array / slice #

Перебор массивов и слайсов в Go работает одинаково:

// Перебор слайса строк.
data := []string{"apple", "banana", "cherry"}
for i, value := range data {
	// i меняется от 0 до len(data)-1
	// value меняется от "apple" до "cherry"
}

Начиная с Go 1.23, можно перебрать слайс в обратном порядке с помощью slices.Backwards(s):\

// Перебор слайса строк в обратном порядке.
data := []string{"apple", "banana", "cherry"}
for i, value := range slices.Backward(data) {
	// i меняется от len(data)-1 до 0
	// value меняется от "cherry" до "apple"
}

range по строке #

Строки в Go неизменяемые и хранятся в кодировке UTF-8, при этом:

  • len(text) возвращает число байт в строке, а число символов Unicode можно получить функцией utf8.RuneCountInString(text)
  • text[i] предоставляет доступ к байту строки по индексу i

Однако перебор строки перебирает символы Unicode, а не байты:

text := "День космонавтики 🚀"

// перебор строки по символам Unicode
for i, c := range text {
	// i меняется от 0 до utf8.RuneCountInString(text)-1
	// c меняется от "Д" до "🚀"
	fmt.Printf("%d -> %c\n", i, c)
}

Если строку надо перебрать по байтам, можно сделать это с помощью преобразования []byte(text) либо обычным циклом for:

text := "🚀🇷🇺"

// перебор строки по байтам
for i, c := range []byte(text) {
	// i меняется от 0 до len(text)-1
  // c перебирает байты emoji-символа, закодированного в UTF-8
	fmt.Printf("%d -> %d\n", i, c)
}

// работает аналогично предыдущему циклу
for i, iMax := 0, len(text); i < iMax; i++ {
	fmt.Printf("%d -> %d\n", i, text[i])
}

range по каналу #

Перебор канала означает чтение из него до тех пор, пока канал не будет закрыт отправителем. При этом даже после закрытия отправителем for дочитает значения до конца.

Помните:

  1. закрывать канал может только отправитель (sender), а не получатель (receiver);
  2. если в for...range по каналу передан nil, то goroutine зависнет (выполнение остановится навсегда)
package main

import (
	"fmt"
	"sync"
)

// Печатает: 1 2 3 4 5 6 7 8 9 10
func main() {
	numbers := make(chan int, 3)
	var wg sync.WaitGroup
	wg.Add(2)

	go func(numbers chan<- int) {
		for i := range 10 {
			numbers <- i + 1
		}
		close(numbers)
		wg.Done()
	}(numbers)

	go func(numbers <-chan int) {
		for num := range numbers {
			fmt.Printf("%d ", num)
		}
		wg.Done()
	}(numbers)

	wg.Wait()
}

range по функции #

В Go 1.23 появился for...range по функциям-итераторам:

// Перебирает квадраты целых чисел, начиная с 0.
squares := func(yield func(n int) bool) {
	i := 0
	for yield(i * i) {
		i++
	}
}

for x := range squares {
	// Этот блок внутри for...range станет телом функции,
	//  которая будет передана как параметр `yield` в функцию `squares`.
	if x > 100 {
		// break приведёт к возврату false из функции, переданной в функцию `squares`.
		break
	}
	fmt.Printf("%d ", x)
}

Ссылки #


Сайт atdd.ru — блог разработчика.