Перейти к основному содержимому

Простые приложения на Rust

Разработчику Архитектору

Простые приложения на Rust

Rust подходит для надёжных утилит: компилятор ловит ошибки владения до запуска, cargo собирает бинарник и тесты. Примеры ниже — на стандартной библиотеке; для HTTP в продакшене чаще берут Axum, но для старта достаточно понять Result, match и работу с файлами.

Порядок: после первой программы и типов с владением.


Как запускать примеры из главы

  1. Для примеров с rand / serde: cargo new demo && cd demo, вставьте код в src/main.rs, добавьте зависимости в Cargo.toml.
  2. Команда: cargo run
  3. Один файл без Cargo: rustc src/main.rs && ./main (Linux/macOS) или main.exe (Windows).

Проверка: rustc --version, cargo --version.


Генератор паролей

Как запустить

  • Проект: cargo new password && cd password
  • Cargo.toml: rand = "0.8"
  • Команда: cargo run
  • Результат: одна строка — пароль из 16 символов.

Код ITЗагрузка примера кода…

Разбор:

  • Функция generate_password(len: usize) -> String принимает длину и возвращает уже собранную строку пароля, поэтому контракт функции ясен по сигнатуре.
  • Константа CHARSET хранится как &[u8], это удобный формат для случайного выбора индекса и последующего приведения байта к char.
  • rand::thread_rng() создает генератор случайных чисел, привязанный к текущему потоку выполнения.
  • Цепочка (0..len).map(...).collect() строит пароль декларативно: для каждой позиции выбирается случайный символ и затем все символы собираются в String.
  • Внутри gen_range(0..CHARSET.len()) верхняя граница не включается, что защищает от выхода за пределы массива.
  • На проде такой пример обычно усиливают криптостойким источником случайности (OsRng) и проверкой политики сложности пароля.

В Cargo.toml: rand = "0.8". Для паролей в продакшене предпочтительнее rand с OsRng / crate getrandom.


Сортировщик текстового файла

Как запустить

  • Файл: src/main.rs в проекте cargo new sort_file
  • Подготовка: input.txt в корне проекта; путь в sort_file(...).
  • Команда: cargo run
  • Результат: output.txt с отсортированными строками.

Код ITЗагрузка примера кода…

Разбор:

  • sort_file(input, output) -> io::Result<()> показывает стандартный шаблон для I/O-функций, где ошибка явно возвращается наружу.
  • fs::read_to_string(input)? считывает весь файл в память и сразу пробрасывает io::Error, если чтение не удалось.
  • Цепочка .lines().map(str::trim).filter(...).map(String::from) сначала очищает строки, затем убирает пустые и только после этого материализует их в Vec<String>.
  • lines.sort() сортирует строки in-place по лексикографическому порядку, без создания второй коллекции.
  • fs::write(...) записывает результат обратно в файл, а добавление + "\n" сохраняет перевод строки в конце файла.
  • fn main() -> io::Result<()> вызывает sort_file и пробрасывает ошибки через ?, поэтому пример сразу запускается через cargo run.
  • Такой пример удобно расширять выбором регистра (case-sensitive/case-insensitive) и сортировкой по кастомному компаратору.

Консольный калькулятор

Как запустить

  • Команда: cargo run (проект с этим main)
  • Нужно: терминал; ввод вида 10 + 3 или по строкам — как в коде.
  • Результат: = 13.0 или сообщение об ошибке.

Код ITЗагрузка примера кода…

Разбор:

  • Функция calc(a, b, op) изолирует арифметическую бизнес-логику от кода ввода-вывода, что упрощает тестирование.
  • Внутри match op каждая операция описана отдельной веткой, а return Err(...) мгновенно прерывает вычисление в невалидных сценариях.
  • Условная ветка '/' if b == 0.0 показывает guard в match: оператор совпал, но дополнительно проверяется условие.
  • Цикл loop в main реализует интерактивный режим калькулятора до команды q.
  • Разбор ввода через split_whitespace() и parse() делает формат команды строгим и предсказуемым.
  • stdout().flush()? принудительно выводит приглашение перед чтением, чтобы пользователь видел prompt сразу.

Трекер задач в JSON (serde)

Как запустить

  • Cargo.toml: serde = { version = "1", features = ["derive"] }, serde_json = "1"
  • Команда: cargo run
  • Результат: файл tasks.json в каталоге проекта.

Код ITЗагрузка примера кода…

Разбор:

  • #[derive(Serialize, Deserialize)] автоматически добавляет сериализацию и десериализацию, поэтому структуру Task легко сохранять в JSON и читать обратно.
  • const DB: &str = "tasks.json" централизует имя файла-хранилища и убирает магические строки из функций.
  • В load() цепочка ok().and_then(...).unwrap_or_default() преобразует ошибки чтения и парсинга в "пустой список", сохраняя простоту для учебного сценария.
  • save(tasks — &[Task]) принимает срез, а не Vec, поэтому функция не зависит от конкретного контейнера и не требует владения коллекцией.
  • serde_json::to_string_pretty(tasks)? формирует читаемый JSON, удобный для ручной проверки и ревью.
  • В практическом приложении к этому примеру обычно добавляют уникальные id, обновление задач и явное логирование ошибок чтения.

Cargo.tomlserde = { version = "1", features = ["derive"] }, serde_json = "1".


Минимальный HTTP-сервер (hyper / tiny_http альтернатива — std через TcpListener)

Как запустить

  • Команда: cargo run
  • Проверка: в другом терминале curl http://127.0.0.1:3000/ — JSON в ответе.
  • Остановка: Ctrl+C.

Код ITЗагрузка примера кода…

Разбор:

  • TcpListener::bind("127.0.0.1:3000")? поднимает локальный TCP-сервер и возвращает ошибку, если порт занят или недоступен.
  • В for stream in listener.incoming().flatten() цикл принимает входящие соединения, пропуская ошибочные элементы без падения процесса.
  • handle(mut stream: TcpStream) читает сырой HTTP-запрос в буфер, затем формирует ответ вручную через строку статуса и заголовки.
  • String::from_utf8_lossy позволяет безопасно логировать/разбирать входные байты даже при неидеальной кодировке запроса.
  • Заголовок Content-Length вычисляется из длины body, чтобы клиент понимал, сколько байтов читать в ответе.
  • Этот код полезен как учебная модель HTTP поверх TCP, а для production обычно переходят на axum/actix-web.

Для учебного API дальше переходите на Axum.


Обход каталога

Как запустить

  • Команда: cargo run из каталога, который нужно сканировать (измените путь в scan(...)).
  • Результат: кортеж (число_файлов, байты).

Код ITЗагрузка примера кода…

Разбор:

  • Рекурсивная функция scan(dir, ext) обходит дерево каталогов в глубину и возвращает агрегированный результат (количество_файлов, общий_размер).
  • fs::read_dir(dir)? отдает итератор по директории, а entry? внутри цикла обрабатывает ошибки доступа к отдельным элементам.
  • Проверка path.is_dir() разделяет ветки: либо рекурсивный обход подпапки, либо обработка файла.
  • Условие по path.extension() фильтрует файлы по расширению, а trim_start_matches('.') позволяет передавать ext в двух форматах.
  • Размер файла берется через entry.metadata()?.len(), после чего суммируется в общий счетчик bytes.
  • Такой пример удобно расширить ограничением глубины обхода, исключениями по директориям и параллельным сканированием.

Характерный пример для Rust — Result и match

Как запустить

  • Команда: cargo run или rustc main.rs && ./main
  • Результат: для 80808080; для abc и 0 — сообщения об ошибке.

Код ITЗагрузка примера кода…

Разбор:

  • parse_port демонстрирует идиоматичную валидацию: сначала парсинг строки в число, затем доменное ограничение port != 0.
  • s.parse().map_err(|_| "...".to_string())? преобразует техническую ошибку парсинга в понятное сообщение для пользователя.
  • Возврат Result<u16, String> делает ошибочный путь частью API функции, а не скрытым побочным сценарием.
  • match parse_port(input) в main наглядно разделяет успешный и неуспешный путь, что упрощает диагностику.
  • Перебор массива ["8080", "abc", "0"] показывает проверку как корректного значения, так и двух разных классов ошибок.
  • Этот шаблон удобно переносить в CLI и серверы, где входные параметры всегда требуют строгой валидации.

Ошибки — часть сигнатуры; это отличает Rust от языков с исключениями "на всё".

Мини-сниппет: аргументы командной строки без внешних библиотек:

use std::env;

fn main() {
let mut args = env::args().skip(1);
let input = args.next().unwrap_or_else(|| "input.txt".into());
let output = args.next().unwrap_or_else(|| "output.txt".into());
println!("{input} -> {output}");
}

Разбор:

  • env::args() возвращает итератор строк: первый элемент — имя программы, дальше аргументы пользователя.
  • .skip(1) убирает путь к исполняемому файлу из разбора.
  • unwrap_or_else(|| ...) задаёт значения по умолчанию, если аргумент не передан.
  • Такой каркас легко расширить вызовом sort_file(&input, &output)? из примера выше.

См. также: Cargo · ошибки · async · Axum

Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.