Циклы: while, until, loop, times

Цикл — это повторение действия. В Ruby есть классические while и until, но в идиоматическом коде их часто заменяют итераторами вроде times и each.
Суть: while повторяет тело, пока условие истинно; until — пока ложно; а n.times — самый ruby-style способ повторить действие ровно n раз.

В большинстве языков цикл — основной инструмент перебора. В Ruby философия другая: вместо ручного управления счётчиком вы чаще «отправляете сообщение» коллекции или числу, и оно само организует повторение. Но базовые циклы знать нужно — они незаменимы там, где число итераций заранее неизвестно.

i = 0
while i < 3
  puts "шаг #{i}"
  i += 1
end

# until — пока НЕ выполнено условие
balance = 100
until balance <= 0
  balance -= 30
end

Разбор: times и управление циклом

Самый частый случай — «повторить N раз». Здесь идиоматичен n.times, который к тому же даёт индекс. Внутри любого цикла работают break (выйти полностью) и next (перейти к следующей итерации).

3.times { |i| puts "итерация #{i}" }   # => 0,1,2

(1..10).each do |n|
  next if n.even?    # пропустить чётные
  break if n > 7     # выйти после 7
  puts n             # => 1,3,5,7
end

loop do
  print "введите 'стоп': "
  break if gets.chomp == "стоп"
end

Как работает под капотом

Все эти конструкции — про поток управления: исполнение «крутится» в теле, пока действует условие или пока не встретит break. Метод loop — бесконечный цикл, его всегда нужно прерывать вручную. Важная деталь: loop тихо завершается, если внутри возникает исключение StopIteration — это делает его удобным для работы с энумераторами.

   вход в цикл
       |
       v
   [ условие? ] --ложь--> выход
       | истина
       v
   тело цикла
       |
   next? --> назад к условию (пропуск остатка тела)
   break? --> немедленный выход
       |
       +--> назад к условию

Частые ошибки

  • Бесконечный цикл. Забыли i += 1 или условие никогда не станет ложным — программа зависнет.
  • Off-by-one. Путаница с < и <= в условии даёт на одну итерацию больше или меньше.
  • Ручной while там, где нужен each. Перебирать массив через индексы и while — не по-рубишному и подвержено ошибкам.

Best practices

  • Если число повторений известно — берите n.times, а не while со счётчиком.
  • Для перебора коллекций используйте each и другие итераторы (о них — следующий раздел), а не индексные циклы.
  • while/until оставьте для случаев, где условие выхода динамическое (ввод пользователя, опрос состояния).

Глубже: почему Ruby не любит классический for

Внимательный читатель заметит, что мы почти не упоминаем цикл for, хотя в Ruby он есть. Это не случайность. Идиоматический Ruby избегает for по нескольким причинам. Во-первых, переменная цикла в for «утекает» наружу и остаётся видимой после цикла, тогда как в блоке each она локальна — это чище и безопаснее. Во-вторых, for не даёт ничего, чего не давал бы each, зато выглядит чужеродно на фоне остального кода, построенного на блоках и итераторах. В-третьих, переход на мышление «отправь сообщение коллекции» вместо «управляй счётчиком вручную» — это ключевой сдвиг, который делает из вас рубиста. Поэтому правило простое: для перебора коллекций берите each и методы Enumerable, для повторения N раз — times, для условного повторения — while/until, а про for можете забыть. Этот сдвиг в привычках окупится в следующем разделе, где блоки и Enumerable раскроются во всю мощь и заменят ручные циклы почти полностью.

Итог. while и until управляют повторением по условию, n.times повторяет фиксированное число раз, а break и next управляют ходом цикла. В рубишном коде ручные циклы уступают место итераторам.

Проверьте себя
1. Какой способ повторить действие ровно 5 раз наиболее идиоматичен в Ruby?
Awhile i < 5 со счётчиком
B5.times { ... }
Cfor i in 0..5
Dloop do ... end
2. Что делает оператор next внутри цикла?
AПолностью выходит из цикла
BПереходит к следующей итерации, пропуская остаток тела
CПерезапускает цикл с начала
DЗавершает программу