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 if
fn 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
, for
loop
: Бесконечный циклБесконечный цикл, который выполняется до явного прерывания (обычно с помощью 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
и continue
break
fn main() {
let mut count = 0;
while true {
count += 1;
if count == 5 {
break; // Выход из цикла
}
println!("Счёт: {}", count);
}
}
continue
fn 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!
оставь для случаев, когда программа должна "упасть" из-за ошибки. О нём будет подробнее позже.match
enum 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: условия, сопоставление, циклы и операторы прерывания. Эти инструменты помогут вам строить гибкую логику. Практикуйтесь с примерами и упражнением!