💬 Какие особенности оператора break следует учитывать при использовании в сочетании со switch или select?



Рассмотрим пример. Мы используем switch внутри for. Когда индекс цикла получает значение 2, требуется прервать цикл:



for i := 0; i < 5; i++ {

fmt.Printf("%d ", i)

switch i {

default:

case 2: // Если i равен 2, то вызывается оператор break

break }

}





На первый взгляд код может казаться правильным, но он не приводит к выполнению ожидаемых действий. Оператор break не завершает цикл for, он завершает действие оператора switch. Следовательно, вместо итерации от 0 до 2 код выполняет итерацию от 0 до 4: 0 1 2 3 4.



💡break завершает выполнение самого последнего оператора for, switch или select. И здесь он прерывает действие switch.



Как написать код, который будет прерывать цикл, а не действие оператора switch? Самый идиоматический способ — использовать loop:



loop:  

for i := 0; i < 5; i++ {

fmt.Printf("%d ", i)

switch i {

default:

case 2:

break loop // Прерывается цикл, привязанный к loop, а не к switch }

}





☑️ Здесь мы связываем loop с циклом for. Поскольку мы указываем эту метку в операторе break, то он прерывает цикл, а не действие оператора switch. Новый код выведет 0 1 2, как и требовалось.



Прерывание не того оператора также может произойти и в случае с select, находящимся внутри цикла. В примере ниже мы хотим использовать select с двумя case и выйти из цикла, если контекст отменяется:



for {

select {

case <-ch:

// Какие-то действия

case <-ctx.Done():

break // Цикл прерывается, если контекст отменяется

}

}





Здесь самым внутренним оператором из списка for, switch, select является select, а не for. Поэтому цикл продолжается. Чтобы прервать сам цикл, используем loop:



loop:  

for {

select {

case <-ch:

// Какие-то действия

case <-ctx.Done():

break loop // Прерывается выполнение привязанного к loop цикла, а не select

}

}





Как и ожидалось, break приводит к выходу из цикла, а не к прерыванию выполнения select.