Циклы в Rust
В Rust есть три способа повторять код: loop, while и for. Здесь речь пойдёт о loop — самом простом и прямолинейном из них: он повторяется вечно, пока ты не скажешь ему остановиться.
Простой loop
Заголовок раздела «Простой loop»fn main() { let mut count = 0;
loop { count += 1; println!("{count}");
if count == 3 { break; } }}Вывод:
123Сам по себе loop никогда не останавливается — его завершает break. Без if count == 3 { break; } эта программа печатала бы числа бесконечно.
Возвращаем значение из цикла
Заголовок раздела «Возвращаем значение из цикла»А вот что умеет loop и не умеют while и for: после break можно написать значение, и оно станет результатом всего цикла:
fn main() { let mut count = 0;
let result = loop { count += 1; if count == 10 { break count * 2; // остановиться и вернуть значение } };
println!("{result}");}Вывод:
20Это удобно для задач вроде «пробуй снова, пока не получится, а потом отдай результат» — в настоящем коде такой приём встречается чаще, чем можно подумать.
Обрати внимание на точку с запятой после закрывающей } цикла. Весь loop { ... } — это правая часть let result = ...;, поэтому инструкции let в конце нужна ;, как и в let x = 5;.
Почему loop, а не while true?
Заголовок раздела «Почему loop, а не while true?»Если ты пришёл из другого языка, то, возможно, по привычке напишешь бесконечный цикл как while true { ... }. В Rust это работает, но компилятор выдаёт предупреждение:
warning: denote infinite loops with `loop { ... }` --> src/main.rs:5:5 |5 | while true { | ^^^^^^^^^^ help: use `loop`Оно означает: «обозначай бесконечные циклы через loop { ... }». И дело тут не только в стиле. Rust знает, что тело loop выполняется всегда, а выйти можно только через break. Для while true он этого не предполагает, потому что не смотрит на значение условия. Это важно, когда переменная получает значение внутри цикла:
fn main() { let mut n = 1; let answer; // значения пока нет
loop { if n * n > 50 { answer = n * n; // задаём его прямо перед выходом break; } n += 1; }
println!("Первый квадрат больше 50 — это {answer}");}Вывод:
Первый квадрат больше 50 — это 64С loop Rust видит, что до println! можно добраться только через этот break, а значит, answer всегда задана. Замени loop на while true — и получишь error[E0381]: used binding answer is possibly-uninitialized («переменная answer, возможно, не инициализирована»). Компилятор считает, что цикл может закончиться, так ни разу и не выполнив if.
На практике эту программу аккуратнее всего написать через break со значением, как выше: let answer = loop { ... break n * n; ... };. В любом случае вывод один: если имеешь в виду «вечно, пока я не выйду», пиши loop.
Когда использовать loop
Заголовок раздела «Когда использовать loop»loop подходит, когда заранее неизвестно, сколько раз придётся повторять, — ты ждёшь, пока какое-то условие станет истинным, а не считаешь по диапазону или списку. Для подсчёта или перебора коллекции обычно лучше читаются while и for (о них — в следующих уроках).
Практика
Заголовок раздела «Практика»1. Обратный отсчёт до старта
Заголовок раздела «1. Обратный отсчёт до старта»С помощью loop выведи обратный отсчёт от 5 до 1, а потом Пуск!. Подсказка: начни с let mut n = 5; и выйди через break, когда n станет 0.
Показать решение
fn main() { let mut n = 5;
loop { if n == 0 { break; } println!("{n}..."); n -= 1; }
println!("Пуск!");}Вывод:
5...4...3...2...1...Пуск!Проверка стоит первой, поэтому при n, равном 0, цикл останавливается раньше, чем успеет вывести 0....
2. Первая степень двойки больше 1000
Заголовок раздела «2. Первая степень двойки больше 1000»Начни с 1 и удваивай. С помощью break со значением получи первое число больше 1000 и посчитай, сколько понадобилось удвоений. Подсказка: заведи mut-счётчик снаружи цикла и напиши let power = loop { ... };.
Показать решение
fn main() { let mut value = 1; let mut doublings = 0;
let power = loop { value *= 2; doublings += 1; if value > 1000 { break value; } };
println!("{power}, удвоений: {doublings}");}Вывод:
1024, удвоений: 10break value; завершает цикл и передаёт value в power. Счётчик живёт снаружи цикла, поэтому сохраняет значение и после его окончания.
loopповторяется вечно, пока его не остановитbreak, — всегда проверяй, что выход есть.break value;останавливает цикл и возвращаетvalueкак результат всего выражения цикла.- Пиши
loop, а неwhile true: Rust знает, что телоloopвсегда выполняется, и это помогает ему проверять переменные. - Бери
loop, когда повторяешь «до тех пор, пока что-то не случится», а не когда считаешь заранее известное число раз.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Что не даёт обычному loop работать вечно?
2. Что делает break count * 2; внутри цикла?
3. Когда, судя по этой странице, loop лучше, чем while или for?
4. Что будет, если написать в Rust while true { ... }?
Результат: 0 / 4
Что почитать
Заголовок раздела «Что почитать»- Повторное выполнение кода с помощью циклов — книга «Язык программирования Rust»: официальное введение в
loop,whileиfor(перевод на русский). - Возвращение значений из циклов — книга «Язык программирования Rust»: короткий раздел про
breakсо значением (перевод на русский). - loop — Rust на примерах: запускаемые примеры с
loop,breakиcontinue(перевод на русский). - Возврат из циклов — Rust на примерах: ещё один маленький пример цикла, который возвращает значение (перевод на русский).
- The
loopkeyword — документация стандартной библиотеки (на английском): краткая справка о том, как работают вместеloopиbreak.