🦀 Rust

التحكم في التدفق في Rust

Условия и циклы в Rust: if, match, loop, for с примерами

Представь светофор: зелёный — едешь, красный — ждёшь. Программа тоже принимает решения в зависимости от условий. В Rust для этого есть несколько конструкций — и некоторые удивительно умные!

if — условное выражение (не просто оператор!)

В Rust if — это выражение: оно возвращает значение! В большинстве языков if — просто команда. В Rust это ещё и значение.

fn main() {
    let number = 7;

    if number < 5 {
        println!("Маленькое");
    } else if number < 10 {
        println!("Среднее");   // выведется это
    } else {
        println!("Большое");
    }

    // ВАЖНО: условие ОБЯЗАТЕЛЬНО bool в Rust!
    // if number { ... }  — ОШИБКА! (не как Python/JS)
    if number != 0 {
        println!("Не ноль!");
    }
}

if как выражение

fn main() {
    let age = 16;
    let status = if age >= 18 { "совершеннолетний" } else { "несовершеннолетний" };
    println!("Статус: {}", status);

    let score = 75u32;
    let grade = if score >= 90 { 5 }
                else if score >= 75 { 4 }
                else if score >= 60 { 3 }
                else { 2 };
    println!("Оценка: {}", grade);  // 4

    let max = if 3 > 5 { 3 } else { 5 };
    println!("Максимум: {}", max);  // 5

    // Обе ветки ОБЯЗАНЫ возвращать одинаковый тип:
    // let x = if true { 5 } else { "пять" };  — ОШИБКА!
}

loop — бесконечный цикл с возвратом значения

fn main() {
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2;  // выходим и возвращаем значение!
        }
    };
    println!("counter={}, result={}", counter, result);  // 10, 20

    // Метки для вложенных циклов
    'outer: loop {
        let mut y = 0;
        loop {
            y += 1;
            if y == 3 { continue 'outer; }
            if y > 5   { break 'outer; }
        }
    }
    println!("Готово!");
}

while — цикл с условием

fn main() {
    let mut n = 5;
    while n > 0 {
        println!("{}...", n);
        n -= 1;
    }
    println!("🚀 ПУСК!");

    let mut sum = 0;
    let mut i = 0;
    while sum < 100 {
        i += 1;
        sum += i;
    }
    println!("1+2+...+{} = {} (первая сумма >= 100)", i, sum);
}

for — цикл по коллекции

fn main() {
    let fruits = ["яблоко", "банан", "вишня", "дыня"];

    // Простой обход
    for fruit in fruits {
        println!("Фрукт: {}", fruit);
    }

    // С индексом
    for (i, fruit) in fruits.iter().enumerate() {
        println!("{}: {}", i + 1, fruit);
    }

    // По вектору — & чтобы не перемещать данные
    let numbers = vec![10, 20, 30, 40, 50];
    let mut total = 0;
    for n in &numbers {
        total += n;
    }
    println!("Сумма: {}", total);
    println!("Вектор жив: {:?}", numbers);

    // Изменяем через &mut
    let mut scores = vec![85, 90, 78];
    for s in &mut scores {
        *s += 5;  // * — разыменование ссылки
    }
    println!("С бонусом: {:?}", scores);
}

Диапазоны (Range)

fn main() {
    // 1..10 — от 1 до 9 (НЕ включая 10)
    for i in 1..10 { print!("{} ", i); }
    println!();

    // 1..=10 — включая 10
    for i in 1..=10 { print!("{} ", i); }
    println!();

    // Обратный порядок
    for i in (1..=5).rev() { print!("{} ", i); }
    println!();

    // Шаг step_by
    for i in (0..=20).step_by(2) { print!("{} ", i); }
    println!();

    // Задача Гаусса
    let sum: i32 = (1..=100).sum();
    println!("1+2+...+100 = {}", sum);  // 5050

    // 10 факториал
    let fact: u64 = (1..=10).product();
    println!("10! = {}", fact);  // 3628800

    // Квадраты через map
    let sq: Vec = (1..=5).map(|n| n * n).collect();
    println!("{:?}", sq);  // [1, 4, 9, 16, 25]

    // Алфавит
    let abc: String = (b'a'..=b'z').map(|c| c as char).collect();
    println!("{}", abc);
}

match — замена switch, но умнее!

Компилятор требует обработать все возможные случаи. Забыл — не скомпилируется!

fn main() {
    let day = 3u32;
    let name = match day {
        1 => "Понедельник",
        2 => "Вторник",
        3 => "Среда",
        4 => "Четверг",
        5 => "Пятница",
        6 => "Суббота",
        7 => "Воскресенье",
        _ => "Неверный день",  // _ = всё остальное
    };
    println!("{}", name);

    // Несколько значений через |
    let is_weekend = match day { 6 | 7 => true, _ => false };
    println!("Выходной: {}", is_weekend);

    // Диапазон в match
    let score = 85u32;
    let grade = match score {
        90..=100 => "Отлично",
        75..=89  => "Хорошо",
        60..=74  => "Удовлетворительно",
        0..=59   => "Неудовлетворительно",
        _        => "Неверно",
    };
    println!("Оценка: {}", grade);

    // Захват значения через @
    let n = 13u32;
    let desc = match n {
        num @ 1..=9   => format!("Однозначное: {}", num),
        num @ 10..=99 => format!("Двузначное: {}", num),
        num           => format!("Большое: {}", num),
    };
    println!("{}", desc);

    // Guard (дополнительное условие)
    let pair = (2, -2i32);
    match pair {
        (x, y) if x + y == 0 => println!("{} + {} = 0", x, y),
        (x, _) if x % 2 == 0 => println!("Первое чётное: {}", x),
        _                     => println!("Другое"),
    }
}

match с enum

enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
    Triangle(f64, f64, f64),
}

fn area(s: &Shape) -> f64 {
    match s {
        Shape::Circle(r)         => std::f64::consts::PI * r * r,
        Shape::Rectangle(w, h)   => w * h,
        Shape::Triangle(a, b, c) => {
            let s = (a + b + c) / 2.0;
            (s * (s-a) * (s-b) * (s-c)).sqrt()
        }
    }
}

fn main() {
    let shapes = vec![
        Shape::Circle(5.0),
        Shape::Rectangle(4.0, 6.0),
        Shape::Triangle(3.0, 4.0, 5.0),
    ];
    for shape in &shapes {
        println!("Площадь: {:.2}", area(shape));
    }
}

Практический пример: игра «Угадай число»

fn main() {
    let secret = 42u32;
    let guesses = [25, 60, 35, 45, 42];

    println!("🎮 Угадай число от 1 до 100!");

    let mut won = false;
    for (n, &guess) in guesses.iter().enumerate() {
        let hint = match guess.cmp(&secret) {
            std::cmp::Ordering::Less    => format!("⬆️  {} — меньше", guess),
            std::cmp::Ordering::Greater => format!("⬇️  {} — больше", guess),
            std::cmp::Ordering::Equal   => format!("🎉 {} — УГАДАЛ!", guess),
        };
        println!("Попытка {}: {}", n + 1, hint);
        if guess == secret { won = true; break; }
    }

    println!("{}", if won { "Победа!" } else { "Не угадал" });
}

Типичные ошибки

❌ Условие не bool

if 5 { ... }  // error: expected `bool`, found integer
if 5 != 0 { ... }  // ✅

❌ Неполный match

match x {
    1 => ...,
    2 => ...,
    // error: non-exhaustive patterns
    _ => ...,  // добавь wildcard
}

Итого

if/else if/else — условие и выражение с возвращаемым значением. loop — бесконечный цикл; break значение возвращает результат. while условие. for x in коллекция. 1..10 — диапазон без 10; 1..=10 — включая. match — проверяет все случаи. Метки 'outer: для вложенных циклов.

Дополнительные примеры для практики

Программирование — это навык, который улучшается только с практикой. Вот несколько задач для самостоятельной работы:

Задача 1: Числовые паттерны

fn main() {
    // Выведи пирамиду из звёздочек
    for i in 1..=5 {
        for _ in 0..i { print!("* "); }
        println!();
    }

    // Числа Фибоначчи итеративно (быстрее рекурсии)
    let mut a = 0u64;
    let mut b = 1u64;
    print!("Фибоначчи: ");
    for _ in 0..15 {
        print!("{} ", a);
        let c = a + b;
        a = b;
        b = c;
    }
    println!();

    // Простые числа через решето Эратосфена
    let n = 50usize;
    let mut is_prime = vec![true; n + 1];
    is_prime[0] = false;
    is_prime[1] = false;
    let mut i = 2;
    while i * i <= n {
        if is_prime[i] {
            let mut j = i * i;
            while j <= n {
                is_prime[j] = false;
                j += i;
            }
        }
        i += 1;
    }
    print!("Простые до {}: ", n);
    for k in 2..=n {
        if is_prime[k] { print!("{} ", k); }
    }
    println!();
}

Задача 2: Работа с данными

fn main() {
    let data = vec![
        ("Алиса", 85.0f64),
        ("Боря",  72.0),
        ("Вера",  91.0),
        ("Гриша", 68.0),
        ("Даша",  95.0),
    ];

    // Сортировка по убыванию
    let mut sorted = data.clone();
    sorted.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());

    println!("Топ учеников:");
    for (i, (name, score)) in sorted.iter().enumerate() {
        let medal = match i { 0 => "🥇", 1 => "🥈", 2 => "🥉", _ => "  " };
        println!("{} {} — {:.1}", medal, name, score);
    }

    let avg = data.iter().map(|(_, s)| s).sum::() / data.len() as f64;
    let above: Vec<_> = data.iter().filter(|(_, s)| *s > avg).collect();

    println!("
Средний балл: {:.2}", avg);
    println!("Выше среднего: {}", above.len());
}

Разбор типичных задач

// Палиндром
fn is_palindrome(s: &str) -> bool {
    let chars: Vec = s.chars().collect();
    let n = chars.len();
    for i in 0..n/2 {
        if chars[i] != chars[n-1-i] { return false; }
    }
    true
}

// FizzBuzz
fn fizzbuzz(n: u32) -> String {
    match (n % 3, n % 5) {
        (0, 0) => "FizzBuzz".to_string(),
        (0, _) => "Fizz".to_string(),
        (_, 0) => "Buzz".to_string(),
        _      => n.to_string(),
    }
}

// Подсчёт слов в тексте
fn word_frequencies(text: &str) -> std::collections::HashMap<&str, usize> {
    let mut freq = std::collections::HashMap::new();
    for word in text.split_whitespace() {
        *freq.entry(word).or_insert(0) += 1;
    }
    freq
}

fn main() {
    println!("'racecar' палиндром? {}", is_palindrome("racecar"));
    println!("'hello' палиндром? {}", is_palindrome("hello"));

    for n in 1..=20 { print!("{} ", fizzbuzz(n)); }
    println!();

    let text = "rust rust код код код программирование";
    let freq = word_frequencies(text);
    let mut pairs: Vec<_> = freq.iter().collect();
    pairs.sort_by(|a, b| b.1.cmp(a.1));
    for (word, count) in pairs {
        println!("'{}': {}", word, count);
    }
}

Дополнительные примеры и упражнения

Работа с коллекциями

use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;

fn main() {
    // HashMap — ассоциативный массив
    let mut scores: HashMap> = HashMap::new();
    
    scores.entry("Алиса".to_string()).or_insert_with(Vec::new).push(5);
    scores.entry("Алиса".to_string()).or_insert_with(Vec::new).push(4);
    scores.entry("Боря".to_string()).or_insert_with(Vec::new).push(3);
    scores.entry("Боря".to_string()).or_insert_with(Vec::new).push(4);
    
    for (name, grades) in &scores {
        let avg: f64 = grades.iter().sum::() as f64 / grades.len() as f64;
        println!("{}: avg={:.2} grades={:?}", name, avg, grades);
    }
    
    // HashSet — уникальные значения
    let mut seen: HashSet = HashSet::new();
    let data = vec![1, 2, 2, 3, 3, 3, 4, 4, 4, 4];
    let mut unique = Vec::new();
    for &n in &data {
        if seen.insert(n) {
            unique.push(n);
        }
    }
    println!("Уникальные: {:?}", unique);
    
    // Пересечение и объединение множеств
    let set_a: HashSet<_> = [1, 2, 3, 4, 5].iter().collect();
    let set_b: HashSet<_> = [3, 4, 5, 6, 7].iter().collect();
    
    let intersection: Vec<_> = set_a.intersection(&set_b).collect();
    let union: Vec<_> = set_a.union(&set_b).collect();
    println!("Пересечение: {:?}", intersection);
    println!("Объединение: {:?}", union);
    
    // VecDeque — двусторонняя очередь
    let mut queue: VecDeque = VecDeque::new();
    queue.push_back(1);
    queue.push_back(2);
    queue.push_back(3);
    queue.push_front(0);
    println!("Очередь: {:?}", queue);
    println!("Извлекли спереди: {:?}", queue.pop_front());
}

Обработка ошибок и ? оператор

use std::num::ParseIntError;
use std::fmt;

#[derive(Debug)]
enum AppError {
    ParseError(ParseIntError),
    ValidationError(String),
    NotFound(String),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::ParseError(e)       => write!(f, "Ошибка парсинга: {}", e),
            AppError::ValidationError(msg) => write!(f, "Ошибка валидации: {}", msg),
            AppError::NotFound(key)       => write!(f, "Не найдено: {}", key),
        }
    }
}

impl From for AppError {
    fn from(e: ParseIntError) -> Self { AppError::ParseError(e) }
}

fn parse_positive(s: &str) -> Result {
    let n: i32 = s.trim().parse()?;    // ? преобразует ParseIntError через From
    if n <= 0 {
        return Err(AppError::ValidationError(format!("{} не положительное", n)));
    }
    Ok(n as u32)
}

fn find_square_root(n: &str) -> Result {
    let num = parse_positive(n)?;
    Ok((num as f64).sqrt())
}

fn main() {
    let inputs = ["25", "-5", "abc", "100", "0"];
    for input in inputs {
        match find_square_root(input) {
            Ok(r)  => println!("√{} = {:.4}", input, r),
            Err(e) => println!("Ошибка для '{}': {}", input, e),
        }
    }
}

Итераторы — ленивые вычисления

fn main() {
    let numbers = vec![1i32, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // Цепочка итераторов — ленивая, выполняется при collect/sum/etc
    let result: Vec = numbers.iter()
        .filter(|&&n| n % 2 == 0)       // только чётные: 2,4,6,8,10
        .map(|&n| n * n)                 // квадраты: 4,16,36,64,100
        .filter(|&n| n > 20)             // > 20: 36,64,100
        .map(|n| format!("{}²", (n as f64).sqrt() as i32)) // "6²", "8²", "10²"
        .collect();
    
    println!("{:?}", result);
    
    // zip — объединяем два итератора
    let names = vec!["Алиса", "Боря", "Вера"];
    let grades = vec![4.8, 3.9, 4.5];
    let combined: Vec<_> = names.iter().zip(grades.iter()).collect();
    for (name, grade) in &combined {
        println!("{}: {:.1}", name, grade);
    }
    
    // enumerate + zip
    let numbered: Vec<_> = names.iter()
        .zip(grades.iter())
        .enumerate()
        .map(|(i, (n, g))| format!("{}. {}: {}", i+1, n, g))
        .collect();
    println!("{:?}", numbered);
    
    // scan — промежуточные результаты (как fold, но возвращает итератор)
    let running_sum: Vec = (1..=5)
        .scan(0, |acc, x| { *acc += x; Some(*acc) })
        .collect();
    println!("Нарастающая сумма: {:?}", running_sum);  // [1, 3, 6, 10, 15]
    
    // flat_map — flatten + map
    let words = vec!["hello world", "foo bar baz"];
    let all_words: Vec<&str> = words.iter()
        .flat_map(|s| s.split_whitespace())
        .collect();
    println!("{:?}", all_words);
    
    // take и skip
    let first_5: Vec = (1..).take(5).collect();
    let skip_5: Vec = (1..=10).skip(5).collect();
    println!("take(5): {:?}", first_5);
    println!("skip(5): {:?}", skip_5);
}

Полный проект: студенческая база данных

use std::collections::HashMap;

#[derive(Debug, Clone)]
struct Student {
    name: String,
    class: String,
    grades: HashMap,
}

impl Student {
    fn new(name: &str, class: &str) -> Self {
        Student {
            name: name.to_string(),
            class: class.to_string(),
            grades: HashMap::new(),
        }
    }

    fn add_grade(&mut self, subject: &str, grade: f64) {
        self.grades.insert(subject.to_string(), grade);
    }

    fn gpa(&self) -> f64 {
        if self.grades.is_empty() { return 0.0; }
        self.grades.values().sum::() / self.grades.len() as f64
    }

    fn is_excellent(&self) -> bool {
        self.gpa() >= 4.5 && self.grades.values().all(|&g| g >= 4.0)
    }
}

struct Database {
    students: Vec,
}

impl Database {
    fn new() -> Self { Database { students: Vec::new() } }
    fn add(&mut self, student: Student) { self.students.push(student); }
    
    fn by_class(&self, class: &str) -> Vec<&Student> {
        self.students.iter().filter(|s| s.class == class).collect()
    }
    
    fn top_students(&self, n: usize) -> Vec<&Student> {
        let mut sorted: Vec<&Student> = self.students.iter().collect();
        sorted.sort_by(|a, b| b.gpa().partial_cmp(&a.gpa()).unwrap());
        sorted.into_iter().take(n).collect()
    }
    
    fn class_average(&self, class: &str) -> f64 {
        let class_students: Vec<_> = self.by_class(class);
        if class_students.is_empty() { return 0.0; }
        class_students.iter().map(|s| s.gpa()).sum::() / class_students.len() as f64
    }
}

fn main() {
    let mut db = Database::new();
    
    let mut alice = Student::new("Алиса", "6А");
    alice.add_grade("Математика", 5.0);
    alice.add_grade("Русский", 5.0);
    alice.add_grade("Английский", 4.0);
    
    let mut bob = Student::new("Боря", "6Б");
    bob.add_grade("Математика", 4.0);
    bob.add_grade("Русский", 3.0);
    bob.add_grade("Английский", 4.0);
    
    let mut vera = Student::new("Вера", "6А");
    vera.add_grade("Математика", 5.0);
    vera.add_grade("Русский", 5.0);
    vera.add_grade("Английский", 5.0);
    
    db.add(alice);
    db.add(bob);
    db.add(vera);
    
    println!("=== Класс 6А ===");
    for s in db.by_class("6А") {
        println!("{}: GPA={:.2} Отличник?{}", s.name, s.gpa(), s.is_excellent());
    }
    
    println!("\n=== Топ 2 студента ===");
    for s in db.top_students(2) {
        println!("{} ({}): {:.2}", s.name, s.class, s.gpa());
    }
    
    println!("\nСредний 6А: {:.2}", db.class_average("6А"));
    println!("Средний 6Б: {:.2}", db.class_average("6Б"));
}

Советы для дальнейшего обучения

  • The Rust Book — официальная книга, доступна на doc.rust-lang.org/book
  • Rustlings — интерактивные упражнения: cargo install rustlings
  • Rust by Example — примеры кода: doc.rust-lang.org/rust-by-example
  • Exercism.io — задачи с ментором по треку Rust
  • Crates.io — репозиторий пакетов для Rust
← السابق المتغيرات في Rust التالي → الدوال في Rust