🦀 Rust

Introducción a Rust

Rust для начинающих: самый безопасный язык программирования

Представь, что тебе нужно построить дом. Можно строить быстро и небрежно — дом появится быстро, но через год начнёт разваливаться. А можно строить медленнее, но с умом — каждый кирпич проверен, каждая стена устойчива. Rust — это язык-архитектор: он заставляет тебя строить правильно с самого начала, зато потом программа не «ломается» в самый неподходящий момент.

История Rust: как всё начиналось

В 2006 году сотрудник Mozilla по имени Грейдон Хоар (Graydon Hoare) вернулся домой и обнаружил, что лифт в его доме не работает — из-за банального бага в программном обеспечении, ошибки с памятью. Грейдон подумал: «Почему в 21 веке мы всё ещё не можем писать надёжные программы?» И начал создавать новый язык программирования.

В 2010 году Mozilla официально объявила о проекте Rust. Название «Rust» (ржавчина) намекает на грибок-паразит, который невероятно живуч и стоек — такими же должны быть программы на этом языке.

С 2016 по 2024 год Rust занимает первое место в опросах Stack Overflow как «самый любимый язык программирования» — 8 лет подряд! Это означает: разработчики, попробовавшие Rust, не хотят возвращаться к другим языкам.

В 2022 году ядро Linux официально приняло Rust как второй язык (после C). Microsoft, Google и NSA рекомендуют использовать Rust вместо C/C++ для нового кода. Amazon Web Services использует Rust для критически важных компонентов своей инфраструктуры.

Зачем нужен Rust? Проблемы старых языков

По данным Microsoft, около 70% всех уязвимостей безопасности в их продуктах связаны с ошибками работы с памятью — проблемами языков C и C++. Google сообщает о схожей статистике в Android. Rust решает эту проблему полностью — на уровне языка, не позволяя программисту допустить такие ошибки.

Самое удивительное: Rust достигает безопасности памяти без сборщика мусора (GC). Сборщик мусора — это как уборщик, который ходит за тобой и убирает мусор. Он надёжен, но тормозит программу в случайные моменты. Rust умнее: он определяет на этапе компиляции, когда память больше не нужна, и убирает её автоматически через систему ownership.

ПроблемаC/C++Java/PythonRust
Ошибки с памятью❌ Часто✅ Редко✅ Невозможны
Скорость выполнения✅ Очень быстро⚠️ Медленнее✅ Очень быстро
Сборщик мусора❌ Нет✅ Есть, но тормозит❌ Не нужен!
Безопасная параллельность⚠️ Сложно⚠️ Сложно✅ Гарантировано

Где используется Rust?

🌐 WebAssembly: Rust — один из лучших языков для компиляции в WebAssembly (Wasm). Figma использует Rust+Wasm для своего дизайн-инструмента прямо в браузере.

🖥️ Операционные системы: Linux kernel поддерживает Rust с 2022 года. Google использует Rust в Android. Существует ОС Redox OS, полностью написанная на Rust.

🦊 Firefox: Mozilla написала на Rust CSS-движок Stylo и рендеринг WebRender — Firefox стал быстрее и безопаснее.

☁️ Облачные сервисы: Amazon AWS, Microsoft Azure, Cloudflare используют Rust для критических компонентов. Cloudflare написала на Rust прокси-сервер Pingora, обрабатывающий триллионы запросов.

⚙️ Системные утилиты: ripgrep (быстрый поиск по файлам), bat (красивый аналог cat), exa (красивый аналог ls) — популярные инструменты командной строки на Rust.

🎮 Игры: Игровой движок Bevy написан на Rust. Discord переписал часть серверов с Go на Rust и получил в 10 раз меньше задержек и лучшее использование памяти.

🔗 Блокчейн: Solana, Near Protocol написаны на Rust ради безопасности и высокой производительности.

Установка Rust: rustup и Cargo

Для установки Rust используется инструмент rustup — менеджер версий.

# Установка на Linux/macOS — одна команда
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Обновляем переменные окружения
source $HOME/.cargo/env

# Проверяем установку
rustc --version   # rustc 1.75.0 (82e1608df 2023-12-21)
cargo --version   # cargo 1.75.0

# Обновление до последней версии
rustup update

На Windows: скачайте и запустите rustup-init.exe с сайта rustup.rs.

Что такое Cargo?

Cargo — это система сборки и менеджер пакетов Rust. Он умеет всё: создавать проекты, компилировать код, запускать программы, тестировать, скачивать библиотеки.

# Создаём новый проект
cargo new hello_rust
cd hello_rust

# Структура проекта:
# hello_rust/
#   Cargo.toml    — описание проекта (имя, версия, зависимости)
#   src/
#     main.rs     — главный файл с кодом

# Запускаем
cargo run

# Только проверка (без компиляции — быстро!)
cargo check

# Тесты
cargo test

# Оптимизированная сборка
cargo build --release

Файл Cargo.toml

[package]
name = "hello_rust"       # имя проекта
version = "0.1.0"
edition = "2021"          # стандарт Rust (используй 2021)

[dependencies]
rand = "0.8"              # библиотека для случайных чисел
serde = { version = "1", features = ["derive"] }

Первая программа: разбираем каждую строку

fn main() {
    println!("Hello, world!");
}
  • fn main() — объявление функции. fn = function. main — особое имя: программа всегда начинает с неё.
  • { } — тело функции, весь код внутри принадлежит main.
  • println! — макрос (объясним ниже). Выводит текст с переносом строки.
  • "Hello, world!" — строковый литерал.
  • ; — точка с запятой завершает выражение.

println! — это макрос, не функция!

Аналогия: Функция — рецепт с фиксированным числом ингредиентов. Макрос — волшебный рецепт, адаптирующийся: нужен торт на 2 человека — пожалуйста, на 100 — тоже работает! Макрос — это код, генерирующий другой код на этапе компиляции.

Поэтому println! — макрос: принимает переменное число аргументов и понимает форматирование.

fn main() {
    // Простой вывод
    println!("Привет, мир!");

    // Вывод переменной — {} является заполнителем
    let name = "Дима";
    println!("Привет, {}!", name);          // Привет, Дима!

    // Несколько переменных
    let age = 12;
    let city = "Москва";
    println!("{} живёт в {} и ему {} лет", name, city, age);

    // Форматирование чисел
    let pi = 3.14159265;
    println!("Число пи = {:.2}", pi);       // 3.14

    // Отладочный вывод {:?}
    let nums = [1, 2, 3, 4, 5];
    println!("Массив: {:?}", nums);         // [1, 2, 3, 4, 5]

    // Красивый вывод {:#?}
    println!("Красиво: {:#?}", nums);

    // Без переноса строки
    print!("Введите имя: ");
}

Другие важные макросы

fn main() {
    // eprintln! — вывод в stderr
    eprintln!("Ошибка: файл не найден!");

    // format! — создаёт String
    let msg = format!("Привет, {}!", "Мир");
    println!("{}", msg);

    // dbg! — отладка с именем переменной
    let x = 5 * 3;
    let r = dbg!(x);    // [src/main.rs:8] x = 15

    // assert! — проверка условия
    assert!(2 + 2 == 4, "Математика сломалась!");

    // vec! — создание вектора
    let v = vec![1, 2, 3];
    println!("{:?}", v);   // [1, 2, 3]

    // todo! — заглушка
    // todo!("Реализовать позже");
}

Комментарии в Rust

// Однострочный комментарий

/*
    Многострочный комментарий
*/

/// Документационный комментарий для cargo doc
/// # Примеры
/// ```
/// let r = add(2, 3);
/// assert_eq!(r, 5);
/// ```
fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let x = 42; // ответ на всё
    println!("Ответ: {}", x);
}

Полная программа с вводом

use std::io;

fn main() {
    println!("╔══════════════════════════╗");
    println!("║  Привет от Rust! 🦀      ║");
    println!("╚══════════════════════════╝");

    println!("\nКак тебя зовут?");
    let mut name = String::new();
    io::stdin().read_line(&mut name).expect("Ошибка чтения");
    let name = name.trim();

    println!("Сколько тебе лет, {}?", name);
    let mut age_str = String::new();
    io::stdin().read_line(&mut age_str).expect("Ошибка");
    let age: u32 = age_str.trim().parse().expect("Введи число!");

    println!("\n--- О тебе ---");
    println!("Имя: {}", name);
    println!("Возраст: {} лет", age);
    println!("Год рождения: ~{}", 2024 - age);
    println!("В следующем году: {} лет", age + 1);
    println!("\nДо свидания, {}! 👋", name);
}

Как работает компиляция?

# Debug-режим (по умолчанию) — быстрая компиляция
cargo build
# target/debug/hello_rust

# Release — медленная компиляция, БЫСТРОЕ выполнение (до 50x!)
cargo build --release
# target/release/hello_rust

Этапы: лексический анализ → синтаксический анализ → семантика (типы, ownership) → генерация кода (LLVM) → линковка.

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

❌ Ошибка 1: Забыл ! у макроса

println("Hello!");    // error: use of undeclared crate `println`
println!("Hello!");   // ✅ ! обязателен

❌ Ошибка 2: Забыл точку с запятой

fn main() {
    println!("Hello!")   // error: expected `;`, found `}`
    println!("Hello!");  // ✅
}

❌ Ошибка 3: Не хватает аргументов форматирования

println!("{} и {}", "Петя");  // error: 2 positional arguments, but 1
println!("{} и {}", "Петя", 10);  // ✅

❌ Ошибка 4: Использование до объявления

println!("{}", x);  // error: cannot find value `x`
let x = 5;          // ✅ объявляй до использования

Итого

Ключевые факты: создан Грейдоном Хоаром в Mozilla в 2010 году. Безопасность памяти без GC. 8 лет подряд — «самый любимый язык» Stack Overflow. Используется в Linux, Firefox, AWS, Cloudflare, WebAssembly.

Инструменты: rustup — менеджер версий. rustc — компилятор. cargo — менеджер проекта и пакетов.

Синтаксис: fn main() — точка входа. println!() — макрос (!) для вывода. {} — заполнитель. ; — конец выражения. // — комментарий.

Следующий урок: переменные — почему они неизменяемы по умолчанию, что такое mut и как работает система типов.

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

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

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