LEARN X · ЗА 20 МИН

Rust

Экспресс-тур по Rust за 20 минут: владение, заимствование, типы, match, Result, трейты и дженерики — весь язык в комментариях кода.

Rust — системный язык с безопасностью памяти без сборщика мусора. Его суперсила — модель владения, которая отлавливает ошибки работы с памятью на этапе компиляции. Ниже весь язык на одной странице: читай комментарии в коде.

1. Структура программы

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

// Точка входа в программу — функция main
fn main() {
    // println! — это макрос (восклицательный знак), а не функция
    println!("Привет, Rust!"); // вывод: Привет, Rust!

    // {} — плейсхолдер для подстановки значения
    println!("2 + 2 = {}", 2 + 2); // вывод: 2 + 2 = 4

    // print! — без перевода строки
    print!("без");
    print!("переноса\n"); // вывод: безпереноса
}
// Компиляция: rustc main.rs && ./main
// Через Cargo: cargo run

2. Переменные и изменяемость

fn main() {
    // По умолчанию переменные НЕИЗМЕНЯЕМЫ (immutable)
    let x = 5;
    // x = 6; // ОШИБКА компиляции: cannot assign twice to immutable variable

    // mut делает переменную изменяемой
    let mut y = 10;
    y = 20; // ок
    println!("y = {}", y); // y = 20

    // Явное указание типа через двоеточие
    let age: u32 = 30;
    println!("age = {}", age); // age = 30

    // const — константа, тип обязателен, вычисляется на этапе компиляции
    const MAX_POINTS: u32 = 100_000; // _ можно для читаемости
    println!("max = {}", MAX_POINTS); // max = 100000

    // Затенение (shadowing) — новая переменная с тем же именем
    let z = 5;
    let z = z + 1;   // z = 6
    let z = z * 2;   // z = 12
    let z = "теперь строка"; // можно даже сменить тип!
    println!("z = {}", z); // z = теперь строка
}

3. Базовые типы и строки

fn main() {
    // Целые: i8 i16 i32 i64 i128 (знаковые), u8..u128 (беззнаковые)
    let a: i32 = -42;
    let b: u8 = 255;

    // Плавающая точка: f32, f64 (по умолчанию f64)
    let pi: f64 = 3.14159;

    // Булев тип
    let flag: bool = true;

    // Символ char — это ОДИН Unicode-символ (4 байта), кавычки одинарные
    let letter: char = 'Я';
    let emoji: char = '🦀';

    println!("{} {} {} {} {} {}", a, b, pi, flag, letter, emoji);
    // вывод: -42 255 3.14159 true Я 🦀

    // Две строковые сущности:
    // &str — строковый срез (string slice), неизменяемая ссылка
    let greeting: &str = "Привет";

    // String — растущая строка, владеет данными в куче
    let mut owned: String = String::from("Привет");
    owned.push_str(", мир"); // можно дополнять
    println!("{}", owned); // Привет, мир

    // &str -> String и обратно
    let s: String = greeting.to_string();
    let back: &str = &s; // ссылка на String -> &str
    println!("{} {}", s, back); // Привет Привет
}

4. Операторы и условия

fn main() {
    // Арифметика: + - * / %
    println!("{}", 7 / 2);  // 3 (целочисленное деление)
    println!("{}", 7 % 2);  // 1 (остаток)
    println!("{}", 7.0 / 2.0); // 3.5

    // Логические: && || !  Сравнение: == != < > <= >=
    let n = 5;

    // if/else — БЕЗ скобок вокруг условия
    if n < 0 {
        println!("отрицательное");
    } else if n == 0 {
        println!("ноль");
    } else {
        println!("положительное"); // сюда
    }

    // if — это ВЫРАЖЕНИЕ, его можно присвоить (аналог тернарника)
    let parity = if n % 2 == 0 { "чёт" } else { "нечёт" };
    println!("{}", parity); // нечёт

    // match — мощный switch (подробнее в секции 11)
    let grade = match n {
        0 => "ноль",
        1..=4 => "мало",      // диапазон включительно
        5 | 6 | 7 => "средне", // несколько значений
        _ => "много",         // _ — все остальные случаи
    };
    println!("{}", grade); // средне
}

5. Циклы

fn main() {
    // loop — бесконечный цикл, выход через break
    let mut i = 0;
    loop {
        i += 1;
        if i == 3 { break; }
    }
    println!("i = {}", i); // i = 3

    // loop может ВОЗВРАЩАТЬ значение через break
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 { break counter * 2; }
    };
    println!("result = {}", result); // result = 20

    // while — пока условие истинно
    let mut n = 3;
    while n > 0 {
        print!("{} ", n); // 3 2 1
        n -= 1;
    }
    println!();

    // for по диапазону: 1..4 — это 1,2,3 (правая граница НЕ входит)
    for x in 1..4 {
        print!("{} ", x); // 1 2 3
    }
    println!();

    // 1..=4 — включительно: 1,2,3,4
    for x in 1..=4 {
        print!("{} ", x); // 1 2 3 4
    }
    println!();

    // for по коллекции
    let arr = [10, 20, 30];
    for v in arr.iter() {
        print!("{} ", v); // 10 20 30
    }
    println!();
}

6. Кортежи и массивы

fn main() {
    // Кортеж (tuple) — фиксированный набор разных типов
    let person: (&str, i32, bool) = ("Аня", 25, true);

    // Доступ по индексу через точку
    println!("{} {} {}", person.0, person.1, person.2);
    // вывод: Аня 25 true

    // Деструктуризация кортежа
    let (name, age, active) = person;
    println!("{} {} {}", name, age, active); // Аня 25 true

    // Массив [тип; длина] — фиксированный размер, один тип
    let nums: [i32; 5] = [1, 2, 3, 4, 5];
    println!("{}", nums[0]);   // 1
    println!("{}", nums.len()); // 5

    // Массив из повторяющихся значений
    let zeros = [0; 3]; // [0, 0, 0]
    println!("{:?}", zeros); // {:?} — отладочный вывод: [0, 0, 0]

    // Срез (slice) — ссылка на часть массива, &arr[start..end]
    let middle = &nums[1..4]; // элементы с индексами 1,2,3
    println!("{:?}", middle); // [2, 3, 4]
}

7. Векторы и коллекции

use std::collections::HashMap;

fn main() {
    // Vec<T> — динамический массив (растущий)
    let mut v: Vec<i32> = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    println!("{:?}", v); // [1, 2, 3]

    // Макрос vec! для быстрого создания
    let mut nums = vec![10, 20, 30];
    nums.push(40);
    println!("len = {}", nums.len()); // len = 4

    // Доступ: индекс (паника при выходе за границы) или .get() -> Option
    println!("{}", nums[0]);          // 10
    println!("{:?}", nums.get(99));    // None (безопасно)

    // HashMap<K, V> — словарь ключ-значение
    let mut scores: HashMap<&str, i32> = HashMap::new();
    scores.insert("Аня", 95);
    scores.insert("Боб", 80);

    // Чтение возвращает Option<&V>
    if let Some(score) = scores.get("Аня") {
        println!("Аня: {}", score); // Аня: 95
    }

    // Перебор пар
    for (name, score) in &scores {
        println!("{} => {}", name, score); // порядок не гарантирован
    }
}

8. Функции

// Функция с параметрами и возвращаемым типом (-> тип)
fn add(a: i32, b: i32) -> i32 {
    // Последнее ВЫРАЖЕНИЕ без точки с запятой — возвращаемое значение
    a + b // НЕТ ; — это return
}

// Явный return тоже работает (для раннего выхода)
fn abs(x: i32) -> i32 {
    if x < 0 {
        return -x; // ранний возврат
    }
    x
}

// Без -> функция возвращает () — "unit", пустой кортеж (как void)
fn greet(name: &str) {
    println!("Привет, {}!", name);
}

fn main() {
    println!("{}", add(2, 3)); // 5
    println!("{}", abs(-7));   // 7
    greet("Rust");             // Привет, Rust!

    // Блок { } — тоже выражение, возвращает последнюю строку
    let y = {
        let t = 5;
        t * t // значение блока
    };
    println!("{}", y); // 25
}

9. Владение и заимствование

Главная фишка Rust. У каждого значения есть один владелец. Когда владелец выходит из области видимости — память освобождается. Это даёт безопасность без сборщика мусора.

fn main() {
    // ВЛАДЕНИЕ (ownership)
    let s1 = String::from("привет");
    let s2 = s1; // s1 ПЕРЕМЕЩЁН (move) в s2, s1 больше не валиден
    // println!("{}", s1); // ОШИБКА: value borrowed after move
    println!("{}", s2); // привет — ок

    // Чтобы скопировать данные, а не переместить — .clone()
    let a = String::from("мир");
    let b = a.clone(); // глубокая копия
    println!("{} {}", a, b); // мир мир — оба валидны

    // Простые типы (i32, bool, char...) КОПИРУЮТСЯ, а не перемещаются
    let x = 5;
    let y = x; // копия
    println!("{} {}", x, y); // 5 5 — оба валидны

    // ЗАИМСТВОВАНИЕ (borrowing) — даём ссылку &, не передавая владение
    let text = String::from("Rust");
    let length = calc_len(&text); // передаём ССЫЛКУ
    println!("'{}' длина {}", text, length); // text всё ещё валиден!
    // вывод: 'Rust' длина 4

    // Изменяемое заимствование &mut — только ОДНО одновременно
    let mut msg = String::from("привет");
    add_excl(&mut msg);
    println!("{}", msg); // привет!
}

// &String — заимствуем, владение остаётся у вызывающего
fn calc_len(s: &String) -> usize {
    s.len()
} // s выходит из области, но данные НЕ освобождаются (мы не владели)

// &mut — изменяемая ссылка
fn add_excl(s: &mut String) {
    s.push('!');
}

10. Структуры и перечисления

// struct — пользовательский тип с именованными полями
struct User {
    name: String,
    age: u32,
    active: bool,
}

// impl — блок реализации методов для типа
impl User {
    // Ассоциированная функция (как конструктор), без self
    fn new(name: &str, age: u32) -> User {
        User { name: name.to_string(), age, active: true }
    }

    // Метод — первый параметр &self (ссылка на экземпляр)
    fn greet(&self) -> String {
        format!("Я {}, мне {}", self.name, self.age)
    }

    // &mut self — метод, изменяющий экземпляр
    fn deactivate(&mut self) {
        self.active = false;
    }
}

// enum — перечисление, может нести данные в вариантах
enum Shape {
    Circle(f64),          // радиус
    Rect(f64, f64),       // ширина, высота
    Point,                // без данных
}

fn area(s: &Shape) -> f64 {
    match s {
        Shape::Circle(r) => 3.14159 * r * r,
        Shape::Rect(w, h) => w * h,
        Shape::Point => 0.0,
    }
}

fn main() {
    let mut u = User::new("Аня", 25);
    println!("{}", u.greet()); // Я Аня, мне 25
    u.deactivate();
    println!("active = {}", u.active); // active = false

    let c = Shape::Circle(2.0);
    let r = Shape::Rect(3.0, 4.0);
    println!("{}", area(&c)); // 12.56636
    println!("{}", area(&r)); // 12
}

11. Сопоставление с образцом

Option<T> заменяет null: либо Some(значение), либо None. Компилятор заставляет обработать оба случая.

fn main() {
    // Option<T> — значение есть (Some) или нет (None)
    let some_num: Option<i32> = Some(7);
    let no_num: Option<i32> = None;

    // match обязан покрыть ВСЕ варианты
    match some_num {
        Some(n) => println!("есть число {}", n), // есть число 7
        None => println!("ничего"),
    }

    // if let — короткая запись, когда интересен один случай
    if let Some(n) = some_num {
        println!("n = {}", n); // n = 7
    }

    // unwrap_or — значение или дефолт, если None
    println!("{}", no_num.unwrap_or(0)); // 0

    // Сложный match с условиями (guard) и привязкой
    let pair = (0, 5);
    match pair {
        (0, y) => println!("на оси Y, y={}", y), // сюда: на оси Y, y=5
        (x, 0) => println!("на оси X, x={}", x),
        (x, y) if x == y => println!("диагональ"),
        _ => println!("где-то ещё"),
    }
}

12. Обработка ошибок

Result<T, E> — успех Ok(значение) или ошибка Err(причина). Оператор ? пробрасывает ошибку наверх.

// Функция возвращает Result: успех или ошибку
fn divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("деление на ноль"))
    } else {
        Ok(a / b)
    }
}

// Оператор ? : если Ok — достаёт значение, если Err — сразу return Err
fn calc() -> Result<f64, String> {
    let x = divide(10.0, 2.0)?; // x = 5.0
    let y = divide(x, 0.0)?;    // здесь вернётся Err и функция прервётся
    Ok(y)
}

fn main() {
    // Обработка через match
    match divide(10.0, 2.0) {
        Ok(v) => println!("= {}", v),  // = 5
        Err(e) => println!("ошибка: {}", e),
    }

    match calc() {
        Ok(v) => println!("= {}", v),
        Err(e) => println!("ошибка: {}", e), // ошибка: деление на ноль
    }

    // panic! — аварийное завершение программы (только для непоправимого)
    let v = vec![1, 2, 3];
    // v[99]; // вызвал бы panic: index out of bounds
    println!("len = {}", v.len()); // len = 3
    // panic!("всё сломалось"); // принудительная паника
}

13. Трейты и дженерики

Трейт (trait) — общее поведение (как интерфейс). Дженерики <T> — код для любого типа.

// Дженерик-функция: работает с любым типом T,
// который умеет сравниваться (ограничение PartialOrd)
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut max = list[0];
    for &item in list.iter() {
        if item > max { max = item; }
    }
    max
}

// trait — описание общего поведения
trait Describe {
    fn describe(&self) -> String;

    // Можно дать реализацию по умолчанию
    fn shout(&self) -> String {
        format!("{}!!!", self.describe())
    }
}

struct Dog;
struct Cat;

// Реализуем трейт для конкретных типов
impl Describe for Dog {
    fn describe(&self) -> String { String::from("Гав") }
}
impl Describe for Cat {
    fn describe(&self) -> String { String::from("Мяу") }
}

// Дженерик-структура: поле любого типа T
struct Wrapper<T> {
    value: T,
}

fn main() {
    let nums = vec![3, 7, 2, 9, 4];
    println!("max = {}", largest(&nums)); // max = 9

    let chars = vec!['a', 'z', 'm'];
    println!("max = {}", largest(&chars)); // max = z

    let d = Dog;
    let c = Cat;
    println!("{}", d.describe()); // Гав
    println!("{}", c.shout());    // Мяу!!! (метод по умолчанию)

    let w = Wrapper { value: 42 };
    println!("{}", w.value); // 42
}
Поддержать проект