exit) в Rust
Примеры: разбор случаев с match
Упражнение: Реализовать конечный автомат с помощью match
Добро пожаловать в четвёртую главу курса по Rust! Сегодня мы разберём управление потоком — ключевой аспект программирования, позволяющий вашему коду принимать решения, повторять действия и обрабатывать различные сценарии. Мы рассмотрим условия (if, else if, else), сопоставление с образцом (match), циклы (loop, while, for), а также операторы break и continue.
Rust сочетает выразительность и безопасность, и управление потоком здесь строго типизировано. Мы разберём каждый элемент с примерами, советами и завершится глава упражнением по созданию конечного автомата с использованием match. Приступим!
if, else if, elseУсловия это как заставить программу выбирать, т.е. позволяют выполнять код в зависимости от истинности выражения. Конструкция if — базовый инструмент ветвления, она как развилка на дороге, помогает программе решать, что делать дальше.:
if условие {
// код выполняется, если условие истинно
} else {
// код выполняется, если условие ложно
}
Условие в if должно быть типа bool. Rust не позволяет использовать числа или другие типы напрямую.
fn main() {
let number = 7;
if number > 0 {
println!("Число положительное");
} else {
println!("Число отрицательное или ноль");
}
}
else iffn main() {
let number = 42;
if number > 100 {
println!("Число больше 100");
} else if number > 0 {
println!("Число положительное, но меньше или равно 100");
} else {
println!("Число отрицательное или ноль");
}
}
if как выражениеВ Rust if может возвращать значение:
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("Значение числа: {}", number); // Вывод: Значение числа: 5
}
Все ветки должны возвращать один тип!
if как выражение для компактности. Для сложных случаев переходите к match.match: Сопоставление с образцомmatch?match — мощный инструмент для сопоставления значения с образцами. Он требует исчерпывающего покрытия случаев т.е. требующий полного охвата всех возможных вариантов:
match значение {
образец1 => выражение1,
образец2 => выражение2,
_ => выражение_по_умолчанию,
}
fn main() {
let number = 2;
match number {
1 => println!("Один"),
2 => println!("Два"),
3 => println!("Три"),
_ => println!("Что-то другое"),
}
}
fn main() {
let number = 42;
match number {
n if n > 0 => println!("Положительное: {}", n),
n if n < 0 => println!("Отрицательное: {}", n),
_ => println!("Ноль"),
}
}
n и откуда оно взялось?n — это имя переменной, которое ты задаёшь прямо в шаблоне match. Оно называется привязкой (binding) в терминологии Rust.
Когда ты пишешь n if n > 0, ты говоришь: "Возьми значение number и назови его n для этой ветки. Затем проверь условие if n > 0. Если оно истинно, выполни действие".
n — это временная переменная, которая существует только внутри конкретной ветки match и принимает значение того, что ты проверяешь (здесь number, то есть 42).
n работает?В match number ты проверяешь значение number. Каждый шаблон в match должен либо:
42 => ...)n), чтобы "захватить" значение и дать ему имя_)Здесь n — это способ захватить значение number и работать с ним в условии if.
Да, вместо n ты можешь использовать любое допустимое имя переменной (букву, слово и т.д.), главное, чтобы оно было осмысленным для тебя и не конфликтовало с другими именами в области видимости. Например:
fn main() {
let number = 42;
match number {
x if x > 0 => println!("Положительное: {}", x),
y if y < 0 => println!("Отрицательное: {}", y),
_ => println!("Ноль"),
}
}
Здесь x и y — просто разные имена для одной и той же идеи: они привязывают значение number в своих ветках.
Ты можешь даже назвать их value, num или как угодно ещё.
Но важно: имя должно быть одинаковым внутри одной ветки (нельзя написать n if x > 0, это вызовет ошибку).
match number берёт значение number (в данном случае 42).n if n > 0.
n к 42.if 42 > 0 — истина.println!("Положительное: {}", 42).match нашёл совпадение.n if n < 0. Не проверяется, потому что первая уже сработала._ — подстановочный знак, ловит всё остальное (в данном случае 0), но до неё не доходит.Если бы number было, например, -5, сработала бы вторая ветка. Если бы number было 0, сработала бы третья (_).
_?match требует исчерпывающего покрытия — ты должен учесть все возможные случаи.
Условия if n > 0 и if n < 0 не покрывают 0, поэтому без _ компилятор выдал бы ошибку. _ — это "ловушка" для всех значений, которые не подошли под предыдущие ветки.
Обычно match используется с точными значениями (например, 1 => ..., 2 => ...), но добавление if позволяет проверять более сложные условия, как в этом примере (положительное, отрицательное или ноль). Это называется guards (охрана) в Rust.
Если тебе не нужно имя внутри ветки, можно обойтись без него, но тогда код будет менее гибким:
match number {
42 => println!("Ровно 42"), // точное значение
_ if number > 0 => println!("Положительное: {}", number),
_ if number < 0 => println!("Отрицательное: {}", number),
_ => println!("Ноль"),
}
Здесь _ используется как "не интересующее нас имя", а значение берётся напрямую из number.
n — это произвольное имя, которое ты придумываешь, чтобы "захватить" значение number в ветке match.x, val, num и т.д.).if и в коде ветки._ код не скомпилируется, так как match требует покрытия всех случаев.match как выражениеfn main() {
let number = 3;
let result = match number {
1 => "один",
2 => "два",
_ => "другое",
};
println!("Результат: {}", result); // Вывод: Результат: другое
}
match вместо длинных цепочек else if.loop, while, forloop: Бесконечный циклБесконечный цикл, который выполняется до явного прерывания (обычно с помощью break). Используется, когда нужно что-то повторять "вечно" или до выполнения условия выхода.
Пример: loop { println!("Повтор"); break; }
fn main() {
let mut count = 0;
loop {
println!("Счёт: {}", count);
count += 1;
if count == 5 {
break;
}
}
}
Программа запускает бесконечный цикл loop, который выводит значение переменной count (начиная с 0) и увеличивает её на 1 на каждой итерации. Когда count достигает 5, условие if count == 5 срабатывает, и цикл прерывается с помощью break. В итоге выводится:
Счёт: 0
Счёт: 1
Счёт: 2
Счёт: 3
Счёт: 4
Можно возвращать значение через break:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("Результат: {}", result); // Вывод: Результат: 20
}
while: Цикл с условиемЦикл с условием — выполняется, пока условие истинно. Удобен, когда количество итераций заранее неизвестно, но есть проверка.
Пример: while x < 5 { x += 1; }
fn main() {
let mut number = 3;
while number != 0 {
println!("Осталось: {}", number);
number -= 1;
}
println!("Взлёт!");
}
Программа использует цикл while, который выполняется, пока переменная number не равна 0. На каждой итерации выводится текущее значение number (начиная с 3), затем оно уменьшается на 1. Когда number становится 0, цикл завершается, и выводится "Взлёт!". Результат:
Осталось: 3
Осталось: 2
Осталось: 1
Взлёт!
for: Итерация по коллекциямЦикл для итерации(перебора) по коллекциям или диапазонам. Самый безопасный и удобный для перебора элементов.
Пример: for i in 0..5 { println!("{}", i); } (выводит 0, 1, 2, 3, 4)
fn main() {
let numbers = [10, 20, 30, 40];
for num in numbers {
println!("Число: {}", num);
}
}
Программа использует цикл for для перебора элементов массива numbers, содержащего значения [10, 20, 30, 40]. На каждой итерации переменная num принимает очередное значение из массива, и оно выводится. Результат:
Число: 10
Число: 20
Число: 30
Число: 40
Для диапазонов:
fn main() {
for i in 1..4 { // от 1 до 3
println!("i = {}", i);
}
for i in 1..=4 { // от 1 до 4 включительно
println!("i = {}", i);
}
}
for для перебора — он безопасен и читаем.break и continuebreakfn main() {
let mut count = 0;
while true {
count += 1;
if count == 5 {
break; // Выход из цикла
}
println!("Счёт: {}", count);
}
}
continuefn main() {
for i in 1..6 {
if i % 2 == 0 {
continue; // Пропускаем чётные числа
}
println!("Нечётное: {}", i);
}
}
exit) в RustВ Rust для "нормального" завершения программы используется функция std::process::exit из стандартной библиотеки. Она завершает программу немедленно с заданным кодом возврата, не вызывая панику.
use std::process;
fn main() {
println!("До выхода");
process::exit(0); // Завершаем программу с кодом 0 (успех)
println!("Это не выведется");
}
Как работает?
process::exit(code: i32) принимает код возврата (i32), который передаётся операционной системе.0 обычно означает "успешное завершение", а ненулевые значения (напримр, 1) — "ошибка".exit программа завершается мгновенно — никакой код ниже не выполняется, даже если есть незавершённые операции.use std::process;
fn main() {
let should_stop = true;
if should_stop {
println!("Пока!");
process::exit(0); // Выходим без ошибок
}
println!("Дальше не дойдём");
}
mainЕсли ты просто хочешь завершить программу "по-хорошему", можно использовать return в main. Это не то же самое, что exit (не мгновенно прерывает), но подходит для естественного завершения:
fn main() {
println!("Работаем...");
return; // Завершаем с кодом 0
println!("Это не выведется");
}
Код возврата по умолчанию — 0, если не указано иное через std::process::Termination.
ResultВ Rust принято возвращать Result из main, чтобы указать успех или ошибку:
fn main() -> Result<(), i32> {
println!("Работаем...");
Ok(()); // Успех, код 0
// или Err(1) для ошибки с кодом 1
}
std::process::exit.main — просто return или Result.panic! оставь для случаев, когда программа должна "упасть" из-за ошибки. О нём будет подробнее позже.matchenum Direction {
North,
East,
South,
West,
}
fn main() {
let dir = Direction::East;
match dir {
Direction::North => println!("Идём на север!"),
Direction::East => println!("Идём на восток!"),
Direction::South => println!("Идём на юг!"),
Direction::West => println!("Идём на запад!"),
}
}
Выведет "Идём на восток!"
С вложенными данными:
enum Coin {
Penny,
Nickel,
Dime,
Quarter(u32),
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(num) => 25 * num,
}
}
fn main() {
let coin = Coin::Quarter(3);
println!("Стоимость: {} центов", value_in_cents(coin)); // Вывод: 75 центов
}
matchЗадача: Реализуйте конечный автомат, моделирующий светофор с состояниями Red, Yellow, Green. Переключайте состояния по таймеру.
enum TrafficLight {
Red,
Yellow,
Green,
}
fn main() {
let mut state = TrafficLight::Red;
let mut timer = 0;
loop {
match state {
TrafficLight::Red => {
println!("Красный: остановитесь!");
if timer >= 3 {
state = TrafficLight::Green;
timer = 0;
}
}
TrafficLight::Green => {
println!("Зелёный: идите!");
if timer >= 5 {
state = TrafficLight::Yellow;
timer = 0;
}
}
TrafficLight::Yellow => {
println!("Жёлтый: приготовьтесь!");
if timer >= 2 {
state = TrafficLight::Red;
timer = 0;
}
}
}
timer += 1;
println!("Таймер: {}", timer);
if timer > 10 {
break; // Для завершения примера
}
}
}
Улучшение: Добавьте ввод пользователя для ручного переключения.
Подсказка: Используйте.
use std::io;
....
let mut input = String::new();
...
io::stdin().read_line(&mut input).expect("Ошибка чтения строки");
...
Мы изучили основы управления потоком в Rust: условия, сопоставление, циклы и операторы прерывания. Эти инструменты помогут вам строить гибкую логику. Практикуйтесь с примерами и упражнением!