Flow Control

IF-ELSE

В Rust есть управление потоком программы через конструкции IF, ELSE IF, ELSE:

let test_number = 6;

if test_number % 4 == 0 {
println!("Divisible by 4");
} else if test_number % 3 == 0 { // Проверка останавливается на первом 
println!("Divisible by 3");      // выполнимом условии, дальнейшие проверки
} else if test_number % 2 == 0 { // пропускаются.
println!("Divisible by 2");
} else {
println!("Number is not divisible by 4, 3 or 2");
}

Конструкция IF является выражением (expression) и возвращает значение:

let condition = true;

let number = if condition { "aa" } else { "ab" }; // присваивание результата IF
println!("Number is {number}");

Используйте if как выражение для компактности. Для сложных случаев лучше переходить к match.

LOOPS

Три варианта организовать цикл: через операторы loop, while, for.

Loop

loop - организация вечных циклов. Конструкция loop является выражением (expression), поэтому возвращает значение.

    let mut counter = 0;
    let result = loop {        
        counter += 1;
        if counter == 10 {
            break counter * 2; // выход из вечного цикла
        }
    }; // ";" нужно, т.к. было выражение
    println!("The result is {result}");

Если делать вложенные циклы, то можно помечать их меткой, чтобы выходить с break на нужный уровень.

    let mut count = 0;
    'counting_up: loop {            // метка внешнего цикла
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;  // goto метка
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");

While

while - цикл с условием. Можно реализовать через loop, но с while сильно короче.

    let mut number = 10;
    while number != 0 {
        println!("{number}!");
        number -= 1;
    }
    println!("ЗАПУСК!");

For

for - цикл по множествам элементов. В том числе можно задать подмножество чисел.

for i in (1..10).rev() { // .rev() - выдача подмножества в обратную сторону
println!("Value: {i}");
}
println!("ЗАПУСК!");

Функции

Вызов функции: указатель на начало функции кладётся на стек (под каждую функцию выделяется место на стеке - stack frame). Стек имеет ограничения по размеру. Можно это проверить, написав пример:

fn main() {
    a();
}

fn a() {
    println!("Calling B!");
    b();
}

fn b() {
    println!("Calling C!");
    c();
}

fn c() {
    println!("Calling A!");
    a(); // бесконечный цикл вызовов
}

При запуске программы она бесконечно вызывает функции, пока не исчерпает место в stack frame функции main, и тогда программа падает с ошибкой:

thread 'main' has overflowed its stack
fatal runtime error: stack overflow
Abort trap: 6

Передача функций как аргументов

Тип функции — её сигнатура.

// Функция apply принимает 2 параметра:
// 1. x типа i32 (32-битное целое число)
// 2. f - функцию с сигнатурой fn(i32) -> i32, 
// т.е. которая принимает i32 и возвращает i32
// Сама apply возвращает i32
fn apply(x: i32, f: fn(i32) -> i32) -> i32 {
    // Применяем переданную функцию f к аргументу x
    // и возвращаем результат
    f(x)
}

// Функция принимает x типа i32 и возвращает x * 2 тоже типа i32
fn double(x: i32) -> i32 {
    x * 2 // удваивает входное число
}

fn main() {
    // Вызываем apply с двумя аргументами:
    // 1. Число 5
    // 2. Функция double
    // double будет применена к 5, то есть 5 * 2 = 10
    let result = apply(5, double);
    println!("Удвоенное: {}", result); // Вывод: Удвоенное: 10
}

Exit

В Rust для “нормального” завершения программы используется функция std::process::exit из стандартной библиотеки. Она завершает программу немедленно с заданным кодом возврата, не вызывая панику.

use std::process;

fn main() {
    println!("До выхода");
    process::exit(0); // Завершаем программу с кодом 0 (успех)
    println!("Это не выведется");
}
  • process::exit(code: i32) принимает код возврата (i32), который передаётся операционной системе.
  • Код 0 обычно означает “успешное завершение”, а ненулевые значения (напримр, 1) — “ошибка”.
  • После вызова exit программа завершается мгновенно — никакой код ниже не выполняется, даже если есть незавершённые операции.

Return

Можно использовать return в main. Это не то же самое, что exit (не мгновенно прерывает), но подходит для естественного завершения:

fn main() {
    println!("Работаем...");
    return; // Завершаем с кодом 0
    println!("Это не выведется");
}

Result

Можно возвращать Result из main, чтобы указать успех или ошибку:

fn main() -> Result<(), i32> {
    println!("Работаем...");
    Ok(()); // Успех, код 0
    // или Err(1) для ошибки с кодом 1
}
  • Если нужно мгновенное завершение с кодом возврата — используй std::process::exit;
  • Если надо завершить программу “правильно” в конце main — просто return или Result;
  • panic! - для случаев, когда программа должна “упасть” из-за ошибки.