Перейти к содержимому

Циклы в Rust

В Rust есть три способа повторять код: loop, while и for. Здесь речь пойдёт о loop — самом простом и прямолинейном из них: он повторяется вечно, пока ты не скажешь ему остановиться.

fn main() {
let mut count = 0;
loop {
count += 1;
println!("{count}");
if count == 3 {
break;
}
}
}

Вывод:

1
2
3
▶ Попробуй сам

Сам по себе 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;.

Если ты пришёл из другого языка, то, возможно, по привычке напишешь бесконечный цикл как 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 подходит, когда заранее неизвестно, сколько раз придётся повторять, — ты ждёшь, пока какое-то условие станет истинным, а не считаешь по диапазону или списку. Для подсчёта или перебора коллекции обычно лучше читаются while и for (о них — в следующих уроках).

С помощью 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....

Начни с 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, удвоений: 10
▶ Попробуй сам

break 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