01

Что такое Rust Incubator?

Пошаговый путь изучения Rust — от основ языка до бэкенд-разработки

Зачем нужен Rust Incubator?

Rust — это язык, который даёт тебе мощь C++ без его ловушек. Rust Incubator — это жёсткий, но чёткий маршрут обучения от самых основ до бэкенд-разработки.

Представьте инкубатор как парник для растений. Проект создаёт идеальные условия для роста ваших навыков Rust: структурированные шаги, практические задания и постепенное усложнение.

📚
Как это работает?

Каждый шаг содержит теорию, примеры кода и задание. Компилятор Rust — ваш строгий наставник: он не пропустит ни одной ошибки.

Структура курса — 5 этапов

Rust Incubator разбит на пять последовательных шагов. Каждый этап опирается на предыдущий — от базового синтаксиса до реальных бэкенд-сервисов.

0
Шаг 0: Основы

Знакомство с синтаксисом, моделью памяти, системой типов. Три дня интенсивной работы с фундаментом языка.

1
Шаг 1: Концепции

Default, Clone, Copy, Box, Pin, Rc, Cell, Send/Sync, Phantom — 9 подшагов по ключевым концепциям Rust.

2
Шаг 2: Идиомы

Newtype, typestates, mem::replace, sealing — 6 подшагов. Учимся писать код так, как принято в Rust-сообществе.

3
Шаг 3: Экосистема

Тестирование, макросы, serde, async, потоки — 11 подшагов по инструментам и библиотекам Rust.

4
Шаг 4: Бэкенд

Базы данных, HTTP-серверы, GraphQL API — 3 подшага. Собираем всё вместе в реальный бэкенд-сервис.

Как устроен проект

Репозиторий организован как Cargo workspace. Каждая папка — отдельный этап обучения со своими подшагами.

rust-incubator/
0_basics/ — Основы: ownership, borrowing, абстракции
day01_ownership_borrowing/
day02_building_abstractions/
day03_memory_management/
1_concepts/ — Ключевые концепции Rust
1_1_default_clone_copy/
1_2_box_pin/
... (9 подшагов)
2_idioms/ — Идиоматичный Rust
3_ecosystem/ — Экосистема и библиотеки
4_backend/ — Бэкенд-разработка
Cargo.toml — Конфигурация workspace

Практика на каждом шаге

Каждый шаг в Rust Incubator — это не просто текст для чтения. Это полноценный учебный блок с теорией, кодом и заданием.

1
README с теорией и ссылками

Каждый подшаг начинается с README-файла, где изложена теория и даны ссылки на официальную документацию и дополнительные материалы.

2
Исходный код с примерами

В папке examples/ находятся готовые примеры, которые можно запустить и изучить. Они демонстрируют концепцию в действии.

3
Задание для самостоятельной реализации

В каждом шаге есть Task — задание, которое нужно выполнить самостоятельно. Это ключевая часть обучения.

4
Тесты для проверки

Написанные заранее тесты проверяют вашу реализацию. Запустите cargo test и убедитесь, что всё работает правильно.

⚠️
Важно

Rust Incubator — не просто набор упражнений. Это целый маршрут, где каждый шаг опирается на предыдущий. Пропускать этапы — значит строить дом без фундамента.

Проверьте себя

1. Сколько основных этапов в Rust Incubator?
2. Что делает каждый шаг в проекте?
3. Для чего нужен этап «Идиомы»?
02

Владение и память

Как Rust управляет памятью без сборщика мусора

📚
Метафора: библиотечная карточка

Каждая книга имеет ровно одного держателя. Хочешь прочитать — возьми на время (borrow). Хочешь передать — верни и отдай карточку другому (move).

Почему это важно

Rust не имеет сборщика мусора (GC). Вместо этого он использует систему ownership, проверяемую на этапе компиляции. Это означает: никаких утечек памяти, никаких гонок данных — и всё это без runtime-затрат.

Три правила владения

1
Один владелец

У каждого значения в Rust есть ровно один владелец (owner).

2
Автоматическое уничтожение

Когда владелец выходит из области видимости — значение уничтожается (Drop).

3
Move или Borrow

Значение можно передать (move) или одолжить (borrow) — но не одновременно изменяемо из двух мест.

Move vs Borrow — код из проекта

Rust fn main() { let s1 = String::from("hi"); let s2 = s1; println!("{}", s2); }
Построчно по-русски
Создаём строку "hi" и кладём в переменную s1
Передаём владение строкой из s1 в s2 (move). Теперь s1 больше недоступна!
Печатаем s2 — это работает, потому что s2 теперь владелец
(после этой строки s2 выходит из области видимости, память освобождается)
Rust fn main() { let s1 = String::from("hi"); print_str(&s1); println!("{}", s1); } fn print_str(s: &String) { println!("{}", s); }
Построчно по-русски
Создаём строку "hi"
Передаём ссылку (&s1) — одалживаем, не отдаём владение
s1 всё ещё жива! Можно использовать дальше
Функция принимает ссылку (&String) — читает, но не владеет
Печатает строку через ссылку

Компилятор как строгий библиотекарь

0 / 9 messages
🔒
Fearless concurrency

В C++ можно случайно обратиться к освобождённой памяти (use-after-free). В Rust компилятор просто не даст скомпилировать такой код. Это и есть fearless concurrency — бесстрашная многопоточность.

Проверьте себя

1. Что произойдёт, если после let s2 = s1; попробовать использовать s1?

2. Чем &T отличается от &mut T?

3. Зачем Rust использует ownership вместо GC?

03

Ключевые концепции

Default, Clone, Copy, Box, Rc, Cell — инструменты для управления данными

Зачем нужны эти концепции

В модуле 2 мы узнали, что у каждого значения один владелец. Но что, если нужно скопировать данные? Или иметь несколько владельцев? Или менять данные через неизменяемую ссылку? Для этого в Rust есть набор трейтов и умных указателей.

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

📚
Зачем столько инструментов?

Rust не использует сборщик мусора. Вместо этого он даёт вам точные инструменты для управления памятью. Каждый трейт — это договор между вами и компилятором: «Я обещаю, что мой тип умеет делать вот это».

Copy vs Clone — код из проекта

Посмотрим на реальный код из проекта Rust Incubator. Две структуры — Point и Polyline — демонстрируют разницу между Copy и Clone.

1_concepts/1_1_default_clone_copy/src/main.rs
// Определяем структуру Point
#[derive(Debug, Copy, Clone, Default, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}

// Определяем структуру Polyline
#[derive(Debug, Clone)]
struct Polyline {
    points: Vec<Point>,
}
НА РУССКОМ

Определяем структуру Point с двумя координатами

#[derive(...)] — автоматически реализует трейты: Copy (побитовое копирование), Clone (глубокое копирование), Default (значение по умолчанию: x = 0.0, y = 0.0)

x и y — числа с плавающей точкой (f64). Простые типы фиксированного размера — значит Copy возможен

Polyline — набор точек, образующих ломаную линию

Vec<Point> хранится в куче (heap) — значит Copy невозможен, только Clone. Копирование Vec означает выделение новой памяти и копирование всех элементов

Семейство умных указателей

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

📦
Box<T>

Кладёт данные в кучу (heap). Один владелец, как обычно. Нужен для рекурсивных типов или больших данных, которые дорого копировать на стеке.

🔗
Rc<T>

Счётчик ссылок. Несколько владельцев одних данных. Только для одного потока! Когда счётчик достигает нуля, данные уничтожаются.

⚡
Arc<T>

Атомарный Rc. Как Rc, но безопасен для многопоточности. Дороже по производительности из-за атомарных операций.

🔓
Cell<T> / RefCell<T>

Внутренняя мутабельность. Позволяют менять данные через &T (неизменяемую ссылку). Проверка заимствований — в runtime, не в compile time.

Rc и общее владение

Как работает Rc<T> — счётчик ссылок? Проследим шаг за шагом, как две переменные делят одни данные.

A
Переменная A
🔗
Rc<T>
B
Переменная B
🗃
Данные
Нажмите «Следующий шаг» для начала

Send и Sync — безопасность потоков

Rust проверяет безопасность многопоточного кода на этапе компиляции. Два маркерных трейта — Send и Sync — определяют, какие типы можно использовать в многопоточном контексте.

1_concepts/1_8_thread_safety/src/main.rs
pub struct SyncAndSend<T> {
    data: Arc<Mutex<T>>,
    _phantom: PhantomData<T>,
}
НА РУССКОМ

SyncAndSend<T> — тип, безопасный для передачи (Send) и разделения (Sync) между потоками

Arc<Mutex<T>> — атомарный счётчик ссылок + блокировка для эксклюзивного доступа к данным. Arc даёт общее владение между потоками, Mutex даёт безопасную мутабельность

PhantomData<T> — фантомный тип, сообщает компилятору дополнительную информацию о T. Не занимает памяти, но важен для корректности проверок

💡
Правило большого пальца

Если данные живут в одном потоке — используйте Rc<RefCell<T>>. Если данные делятся между потоками — используйте Arc<Mutex<T>>. Это два главных паттерна общего владения в Rust.

Проверьте себя

1. В чём разница между Copy и Clone?
2. Зачем нужен Rc<T>, если есть обычные ссылки?
3. Что произойдёт, если попытаться отправить Rc<T> в другой поток?
04

Идиомы и паттерны

Как писать код, который Rust-сообщество назовёт идиоматичным

Зачем нужны идиомы

Представьте правила дорожного движения. Технически машина может ехать по встречке — но так никто не делает. Идиомы — это правила, по которым пишут код профессионалы.

Компилятор Rust гарантирует безопасность памяти, но не гарантирует качество дизайна. Идиомы — это проверенные паттерны, которые делают код надёжнее, читаемее и удобнее в использовании.

Хороший API трудно использовать неправильно. Идиоматичный Rust-код превращает потенциальные ошибки программиста в ошибки компиляции — задолго до того, как код попадёт в продакшн.

Newtype — типы вместо ошибок

Начнём с реального бага. Посмотрите на структуру Post и функцию repost — видите проблему?

Rust — версия с багом
#[derive(Clone)]
struct Post {
    id: u64,
    user_id: u64,
    title: String,
    body: String,
}

fn repost(post: &Post, new_author_id: u64) -> Post {
    let mut new_post = post.clone();
    new_post.id = new_author_id;  // Oops!
    new_post
}
Что тут происходит
Структура Post: поля id и user_id — оба u64. Компилятор не видит разницы!
Поля title и body — оба String. Тоже легко перепутать.
Функция repost должна обновить user_id...
...но мы случайно записали new_author_id в поле id. Баг! И компилятор молчит.

Решение — паттерн Newtype. Обернём каждое значение в свой тип:

Rust — исправленная версия
mod post {
    #[derive(Clone, Debug, PartialEq, Eq)]
    pub struct Id(u64);

    #[derive(Clone, Debug, PartialEq, Eq)]
    pub struct Title(String);
}

mod user {
    #[derive(Clone, Debug, PartialEq, Eq)]
    pub struct Id(u64);
}
Что изменилось
Создаём отдельные типы post::Id и user::Id
Оба хранят u64 внутри, но компилятор считает их разными типами
Теперь перепутать post::Id и user::Id — ошибка компиляции!
Zero-cost: в runtime никаких накладных расходов

Typestates — машина состояний в типах

А что если нужно гарантировать порядок операций? Например, пост проходит модерацию: его нельзя удалить до публикации и нельзя опубликовать повторно. Паттерн Typestates кодирует эти правила прямо в системе типов.

N
New — новый пост

Только что создан. Единственное доступное действие — publish(), которое отправит пост на модерацию.

U
Unmoderated — на модерации

Ожидает решения модератора. Доступны два действия: allow() — одобрить, deny() — отклонить.

P
Published — опубликован

Пост доступен читателям. Единственное доступное действие — delete().

D
Deleted — удалён

Финальное состояние. Никаких действий больше не доступно.

Вот как это выглядит в коде:

Rust
impl Post<New> {
    pub fn publish(self) -> Post<Unmoderated> {
        // ...
    }
}

impl Post<Unmoderated> {
    pub fn allow(self) -> Post<Published> {
        // ...
    }
    pub fn deny(self) -> Post<Deleted> {
        // ...
    }
}

impl Post<Published> {
    pub fn delete(self) -> Post<Deleted> {
        // ...
    }
}
Построчный разбор
publish() доступен ТОЛЬКО для Post<New>. Нельзя опубликовать уже опубликованный пост!
allow() и deny() — только для Post<Unmoderated>
Каждый переход потребляет (self) старое состояние и создаёт новое
Невозможные переходы — ошибка компиляции. Не runtime, а на этапе сборки!
💡
Zero-cost абстракции в действии

Паттерн Newtype не стоит ничего в runtime — это чисто компиляторная абстракция. Но он предотвращает целый класс багов: перепутанные ID, невалидные email-адреса, неправильные единицы измерения. Чем больше семантики в типах — тем меньше багов в коде.

Практические задания из проекта

В Rust Incubator на этапе идиом вас ждут два ключевых задания. Оба требуют применения изученных паттернов на практике.

🔗
Задание 1: Потокобезопасный двусвязный список

Реализуйте doubly linked list, который можно безопасно использовать из нескольких потоков. Это задание проверяет понимание ownership, interior mutability и Send/Sync — всех концепций из предыдущих модулей.

🍮
Задание 2: VendingMachine — автомат с продуктами

Спроектируйте торговый автомат с применением typestates и newtype-паттернов. Это задание на проектирование API, где невозможные состояния становятся невозможными.

🛠
Требования к VendingMachine

Каждый продукт (Product) имеет цену и название. Автомат имеет ограниченную ёмкость. Должен выдавать сдачу и отклонять покупку, если сдачу выдать невозможно. Номиналы монет: 1, 2, 5, 10, 20, 50.

Проверьте себя

1. Зачем оборачивать u64 в newtype вроде post::Id?
2. Что произойдёт, если вызвать delete() на Post<New>?
3. Почему Newtype называют zero-cost abstraction?
05

Экосистема Rust

Тестирование, макросы, сериализация, async и все, что нужно для реальных проектов

📚
Метафора: швейцарский армейский нож

Сам по себе Rust — это лезвие. Но экосистема библиотек (crates) — это все остальные инструменты: ножницы, отвертка, штопор. Без них далеко не уедешь.

11 подшагов экосистемы

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

3.1
Тестирование

#[test], mockall, proptest. BDD-стиль тестов для описания поведения системы.

3.2
Макросы

macro_rules! и proc_macro. Генерация кода на этапе компиляции для устранения boilerplate.

3.6
Serde

Сериализация/десериализация: JSON, TOML, YAML и любой другой формат данных.

3.8
Логирование

tracing, structured logging. Переменная RUST_LOG для управления уровнями логирования.

3.10
Потоки

std::thread, rayon для параллелизма. Mutex, RwLock для синхронизации доступа к данным.

3.11
Async

tokio, futures, async/await. Тысячи задач на одном потоке без блокировки.

Serde — король сериализации

Serde — самая скачиваемая библиотека Rust. Одна аннотация #[derive(Serialize, Deserialize)] — и ваша структура умеет превращаться в JSON, TOML, YAML, MessagePack и десятки других форматов.

Rust use serde::{Serialize, Deserialize}; #[derive(Debug, Serialize, Deserialize)] struct Config { host: String, port: u16, debug: bool, }
Построчно по-русски
Подключаем трейты Serialize и Deserialize из библиотеки serde
derive автоматически генерирует код для сериализации на этапе компиляции
Структура Config с тремя полями: host (строка), port (число), debug (флаг)
Теперь Config умеет превращаться в JSON: {"host":"...", "port":8080, "debug":true}
И обратно: из строки JSON можно восстановить структуру Config

Async — тысячи задач на одном потоке

Асинхронность в Rust основана на Future и кооперативной многозадачности. Вместо того чтобы создавать тысячи потоков ОС, runtime (tokio) управляет тысячами легковесных задач на нескольких потоках.

👤
Пользователь
⚙️
Tokio Runtime
📦
Future (задача)
💻
Операционная система
Нажмите «Следующий шаг»

Финальный проект Step 3 — CLI-утилита

Итоговый проект этапа экосистемы — настоящая CLI-утилита для обработки JPEG-изображений: удаление метаданных и минимизация размера файла. Здесь сходятся все изученные темы: CLI-парсинг, конфигурация, параллелизм, логирование.

1
Ввод из разных источников

Принимает файлы и URL через CLI-аргументы, stdin или файл-список. Используются библиотеки clap и reqwest.

2
Параллельная обработка

Несколько изображений обрабатываются одновременно с помощью rayon или tokio — без блокировки основного потока.

3
Настраиваемое качество

Параметр качества JPEG на выходе задается пользователем. Баланс между размером файла и визуальным качеством.

4
Каскадная конфигурация

Настройки загружаются из файла конфигурации, переменных окружения и CLI-аргументов — с приоритетом в этом порядке.

5
Управляемое логирование

Поддержка RUST_LOG для управления уровнями логирования: от тихого режима до подробной отладки через tracing.

⚡
Async — это не магия

Async в Rust — это кооперативная многозадачность: каждая задача сама решает, когда уступить управление. В отличие от потоков ОС, переключение между Future практически бесплатно. Поэтому Tokio может обрабатывать сотни тысяч соединений на одном сервере.

Проверьте себя

1. Что делает #[derive(Serialize, Deserialize)]?

2. Чем async/await отличается от обычных потоков?

3. Почему экосистема в Rust Incubator занимает больше всего шагов?

06

Бэкенд на Rust

Базы данных, HTTP-серверы и GraphQL API — финальный рубеж

📚
Метафора: диспетчерская башня аэропорта

Бэкенд — это центр управления: принимает запросы (самолёты), проверяет их, направляет к нужным данным (терминалам) и отправляет ответы обратно. Каждый компонент системы — HTTP-сервер, GraphQL-резолвер, база данных — это отдельная служба аэропорта, работающая слаженно.

Три столпа бэкенда

Rust Incubator завершается тремя шагами, которые покрывают весь стек бэкенд-разработки.

🗃
4.1 Базы данных

diesel, sqlx, sea-orm. Пулы соединений, миграции, ORM

🌐
4.2 HTTP

actix-web, axum, hyper. Серверы и клиенты, WebSocket

🚀
4.3 API

GraphQL (async-graphql), REST, gRPC. Проектирование API

Финальный проект — GraphQL API сервер

Кульминация всего курса. Финальный проект объединяет всё, что вы изучили: систему типов, async/await, работу с базой данных и HTTP. Вот требования:

1
Модель данных

User с полями id, name и список friends (другие User). Рекурсивная структура — друзья тоже имеют друзей.

2
Аутентификация по паролю

Пользователь вводит логин и пароль, сервер возвращает токен авторизации. Пароли хранятся в хешированном виде.

3
Регистрация пользователей

Мутация для создания нового пользователя с именем и паролем. Валидация входных данных.

4
Получение пользователя с друзьями

Запрос пользователя со всеми друзьями (и друзьями друзей) — требует авторизации. Поддержка вложенных запросов.

5
Добавление и удаление друзей

Мутации для управления списком друзей — требуют авторизации. Только авторизованный пользователь может изменять свой список.

Стек технологий

Как все компоненты работают вместе? Проследим путь GraphQL-запроса от клиента до базы данных и обратно.

✈
Клиент
🌐
axum/actix
🔎
GraphQL Resolver
🗃
База данных
Нажмите «Следующий шаг» для начала

Почему Rust для бэкенда

Rust сочетает три качества, которые редко встречаются вместе в одном языке.

⚡
Производительность

Скорость C/C++ без сборщика мусора. Предсказуемая латентность, нет пауз GC. Серверы на Rust обрабатывают миллионы запросов с минимальным потреблением памяти.

🔒
Безопасность

Никаких data races, null pointer dereference или buffer overflow. Компилятор гарантирует корректность на этапе сборки, а не в продакшене.

💫
Надёжность

Если скомпилировалось — с высокой вероятностью работает корректно. Паттерн Result<T, E> вместо исключений. Ошибки обрабатываются явно, а не прячутся в стеке вызовов.

⚠️
Важно: depth limiting

GraphQL решает проблему REST — over-fetching и under-fetching. Клиент сам описывает, какие именно данные ему нужны. Но это создаёт новую проблему: вложенные запросы могут убить базу данных. Запрос friends { friends { friends { ... } } } генерирует экспоненциальное количество SQL-запросов. Поэтому в задании предлагают реализовать depth limiting — ограничение глубины вложенности запросов.

Итоги курса

Пройдём весь путь Rust Incubator от начала до конца — от первых строк кода до production-ready бэкенда.

1
Основы

Владение, заимствование, система типов — фундамент, на котором строится всё остальное.

2
Концепции

Умные указатели, потокобезопасность, фантомные типы — глубокое понимание механизмов Rust.

3
Идиомы

Newtype, typestates, zero-cost абстракции — как писать код так, как принято в Rust-сообществе.

4
Экосистема

Тестирование, serde, async, CLI-инструменты — библиотеки и инструменты для реальных проектов.

5
Бэкенд

HTTP, базы данных, GraphQL API — всё вместе в финальном проекте.

🏆
Финальное слово

Rust Incubator — это не просто учебник. Это маршрут трансформации: от человека, который слышал про Rust, до разработчика, способного создать production-ready бэкенд. Каждый шаг имеет значение.

Проверьте себя

1. Почему GraphQL используется вместо REST в финальном проекте?

2. Что такое depth limiting в GraphQL?

3. Почему Rust подходит для бэкенд-разработки?