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

if let и while let в Rust

Ты уже обрабатывал Option и Result через match. Но чаще всего на деле тебе важен только один образец, а всё остальное можно пропустить. Полный match с веткой _ => {}, которая ничего не делает, работает, но это больше церемоний, чем нужно. if let — короткий путь.

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("Возраст: {value}"),
_ => {}, // этот случай нам на самом деле не важен
}
}

_ => {} ничего не делает — он нужен только для того, чтобы выполнить правило match «каждый случай должен быть обработан». Если ты хочешь что-то сделать только для Some, такого кода слишком много для такой простой мысли.

fn main() {
let age: Option<i32> = Some(30);
if let Some(value) = age {
println!("Возраст: {value}");
}
}

Вывод:

Возраст: 30
▶ Попробуй сам

Читай это так: «если age подходит под образец Some(value), выполни этот блок, а в value положи то, что было внутри». Если age равно None, блок просто пропускается — без ошибки и без вывода.

У if let может быть else, как у обычного if:

fn main() {
let age: Option<i32> = None;
if let Some(value) = age {
println!("Возраст: {value}");
} else {
println!("Возраст не указан");
}
}
▶ Попробуй сам

С Result, с которым ты познакомился в уроке Result, всё работает точно так же:

fn main() {
let input = "42";
if let Ok(number) = input.parse::<i32>() {
println!("Получилось число: {number}");
} else {
println!("Не удалось разобрать");
}
}
▶ Попробуй сам

Как у if есть else if, так и за if let может идти else if let. Rust пробует образцы по очереди и выполняет первый подходящий блок. Это удобно, когда значение можно найти в нескольких местах и они идут в порядке предпочтения:

fn main() {
let nickname: Option<&str> = None;
let full_name: Option<&str> = Some("Феррис Краб");
if let Some(nick) = nickname {
println!("Привет, {nick}!");
} else if let Some(name) = full_name {
println!("Здравствуйте, {name}.");
} else {
println!("Здравствуй, незнакомец.");
}
}

Вывод:

Здравствуйте, Феррис Краб.
▶ Попробуй сам

nickname равно None, поэтому первый блок пропускается. full_name подходит под Some(name), поэтому выполняется второй блок, а до последнего else дело не доходит. В любое место цепочки можно добавить и обычное условие, например else if is_admin { ... }.

Каждая переменная существует только внутри своего блока: nick нельзя использовать во втором блоке, а name — после окончания цепочки. Если распакованное значение нужно в остальном коде, посмотри на let-else в уроке Сопоставление с образцом.

while let применяет ту же идею к циклу: цикл продолжается, пока значение подходит под образец. Особенно удобно так опустошать коллекцию:

fn main() {
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
}

Вывод:

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

.pop() возвращает Some(value), пока в векторе что-то есть, и None, когда он опустел, — в этот момент while let сам останавливает цикл. Проверять длину вручную не нужно.

Используй match, когда действительно нужно по-разному обработать несколько случаев. Бери if let, когда тебе важен ровно один образец, а всё остальное можно просто пропустить. Это не замена match, а лишь более короткая запись частого случая «мне нужна только одна ветка».

Начни с let mut numbers = vec![4, 8, 15]; и счётчика total, равного 0. С помощью while let и .pop() снимай числа с конца по одному, прибавляй каждое к total и выводи число и новую сумму. Подсказка: total нужен mut, а total += n прибавляет к нему число.

Показать решение
fn main() {
let mut numbers = vec![4, 8, 15];
let mut total = 0;
while let Some(n) = numbers.pop() {
total += n;
println!("взяли {n}, сумма теперь {total}");
}
}

Вывод:

взяли 15, сумма теперь 15
взяли 8, сумма теперь 23
взяли 4, сумма теперь 27
▶ Попробуй сам

.pop() берёт элементы с конца, поэтому первым выходит 15.

Сохрани let input = "17";. Если строка разбирается как u32, выведи, взрослый это человек (18 или больше) или несовершеннолетний. Если не разбирается — выведи, что это не число. Попробуй заменить input на "abc". Подсказка: положи обычный if / else внутрь блока if let.

Показать решение
fn main() {
let input = "17";
if let Ok(age) = input.parse::<u32>() {
if age >= 18 {
println!("{age} — взрослый");
} else {
println!("{age} — несовершеннолетний");
}
} else {
println!("'{input}' — это не число");
}
}

Вывод:

17 — несовершеннолетний
▶ Попробуй сам

С input = "abc" разбор не удаётся, выполняется внешний else, и программа выводит 'abc' — это не число.

  • if let Образец = значение { ... } выполняет блок, только если значение подходит под Образец, — всё остальное молча пропускается.
  • Добавь else, чтобы обработать неподходящий случай, как у обычного if, а несколько образцов связывай через else if let.
  • while let Образец = значение { ... } крутит цикл, пока значение подходит, — удобно, чтобы опустошить коллекцию через .pop().
  • if let/while let — когда важен один образец; match — когда нужно по-разному обработать несколько случаев.

Проверь себя

1. Что произойдёт, если выполнить if let Some(value) = age, когда age равно None?

2. Когда стоит выбрать match вместо if let?

3. Когда остановится цикл while let Some(top) = stack.pop() { ... }?

Результат: 0 / 3