Проект, в котором я участвовал поставлен на паузу, нового пока не предвидится. Я устал и хочу отдохнуть. У меня накопилось более 30 рабочих дней отпуска, но я думаю мне этого не хватит. Как мне кажется прекрасное время заняться образованием и всякой ерундой и хорошенько побездельничать. Прошло более месяца как я изучаю Rust. Чтобы закрепить знания, после прочтения документации, я решил переписать старые скрипты (php) на Rust с некоторыми улучшениями. Эти скрипты я написал довольно давно и они нужны мне для автоматизации публикации записей в блоге. Я не использую wordpress и мой блог это статичный сайт, то есть, грубо говоря, набор html страниц. Мой процесс создания записи выглядит так, что я пишу черновик на локальной машине, далее публикую, потом добавляю данные в индекс для полнотекстового поиска и после этого выгружаю в облачное хранилище. Таким образом мои записи хранятся в репозитории в виде файлов. Никакой б.д., никакого php и никакого backend (формально). Как раз эту запись я публикую с помощью новой CLI утилиты на Rust, которую я назвал Mashinka.
В этой записи я расскажу про:
-
Почему Rust;
-
Mashinka
-
Процесс переписывания;
-
Впечатления;
-
Планы и результаты;
-
Ссылки;
Почему Rust
Это все маркетинг и реклама. Просто увидел где-то, прочел, что он быстрый как С++, memory safety и без gc и стало интересно. Это как пошел в магазин со списком товаров, но увидел какую-то вкусняшку, решил что она вкусная и купил попробовать. Вопрос почему очень интересный, но с какого-то момента очень сложно дать на него ответ.
Немного о Mashinka
Это CLI утилита, которая из-за своей специфики нужна только мне. Также я решил, что буду добавлять минимум сторонних зависимостей. Итак, есть набор входных параметров, которые после парсинга превращаются в структуры, которые реализуют трейт Command (метод run). Внутри метода run расположена вся логика команды. Метод run возвращает CommandResult. CommandResult содержит имя команды и детали выполнения команды. Команд на данный момент две: Publish и Index. Все команды лежат в модуле command.
Процесс переписывания
Я решил, что буду стримить весь процесс переписывания. Возьму старые скрипты на PHP и перепишу. Но перед этим установил Rust и примерно неделю читал документацию, практиковался и отмечал недочеты, которые обнаружил в русской версии. Я прочитал почти всю документацию, за исключением последней главы и после этого начал вспоминать что я там понаписал в своих скриптах и главное зачем. Походу работы появлялись ребята, которые заходили на стримы и писали комментарии, иногда бывало так, что комментарии уводили в сторону, иногда мы просто общались. Все это увеличивало время переписывания, но это не было тратой времени. Я узнал много всего интересного и познакомился с новыми людьми.
Впечатления
Я люблю строгие и понятные правила. Первое время я ощущал себя человеком, которого бьют по рукам идущего по граблям. То я владение передам, а потом использую переменную, то возвращаю ссылку на переменную, которая вышла за scope. Первое время складывалось впечатление, что я прочел книгу, а на практике собираю все грабли, про которые там писали. Одна штука меня приятно удивила это вывод ошибок компилятора. Читаешь и понимаешь что компилятор пытается общаться с человеком указывая на то, где возникла ошибка и предлагая способ ее исправить. Я бы назвал вывод ошибок компилятора самой крутой, что я видел. Вообще я считаю, что если концепция имеет высокий порог входа, но при этом дает весомый плюс, то на нее стоит обратить внимание. Memory safety это весомый плюс. Однако я бы не выбрал Rust для решения задач на LeetCode или в качестве олимпиадного языка, как раз по причине его строгости. При решении задачи хочется выразить идею, а концептуальный слой, как мне кажется, может это усложнить. Меня не удивил cargo, потому что я считаю, что любой современный язык должен быть подаваться с пакетным менеджером и если его нет, то это проблема.
У меня до сих пор вызывают затруднения модули. Я забываю пометить их public и про то, что нужно создать файл с именем модуля и потом создать директорию с таким же именем. Еще помню разбирался с интеграционными тестами и никак не мог понять, как сделать import из основного приложения. Оказалось что надо создать файл lib.rs и там перечислить все модули, которые должны быть доступны в tests.
Мне очень понравилось то, что в Rust нет исключений. С точки зрения CLI я объявил enum Error и использовал thiserror. Там, где возникают проблемы я возвращаю Error и обрабатываю его на самом верхнем уровне. Все ошибки описаны в одном месте. Использование ? делает обработку ошибок довольно изящной.
Да, кстати, enum это не просто перечисление. Приведу пример для наглядности
/// Список ошибок
#[derive(Error, Debug)]
pub enum Error {
// config
#[error("Check parameter format, please. Should be --param-name or --param-name=value")]
Parse(),
#[error("Check date time format `{0}`")]
DateTimeError(ParseError),
#[error("Value for {0} should be filled (not empty)")]
EmptyValue(String),
}
Таким образом, enum может состоять из элементов, где каждый может хранить значение определенного типа и дальше, в коде, вы можете это использовать. Помимо этого можно написать имплементацию для enum точно так же как и для структуры.
Ах, да, в rust нет классического привычного наследования. И я поначалу был в ступоре от того, как так жить вообще. Но жить можно и даже посещают некоторые крамольные мысли.
А еще в Rust нет null. Есть Option, который может быть None, но это другое. Вы всегда знаете, где будет -этот None, а елси используете match, то компилятор заставит вам обработать этот случай.
А еще я удивился системе макросов. В PHP, Kotlin есть так называемая Reflection. Которая позволяет получать и изменять данные объекта по время исполнения. К примеру получить список имен и значений полей объекта и изменить их, сгенерить объект и т.д. Это используют при написании всяких фреймворков. В Rust нет Reflection, но есть макросы. С помощью них вы можете творить "магию". Можно написать свой собтсвенный макрос, который создаст вместо вас кусок кода, можно пометить поля и изменить их в макросе, можно взять кусок кода и изменить его в макросе и т.д.
Rust - современный, мультипарадигменный язык, в основе которого лежит концепция направленая на обеспечение memory safety, fearless concurrency и современных фишек (итераторы, трейты, жирная стандартная библиотека, функциональщина) без ущерба для производительности. Мне бы хотелось и дальше использовать этот язык и было бы любопытно наблюдать за его развитием.
Планы и результаты
Эту запись я публикую с помощью Mashinka, написанной на Rust. В планах написать команду Deploy и Help. В подвале я нашел Raspberry Pi и планирую использовать ее в качестве сервера. Хочу попробовать Rust в web. Также, после чтения документации я обнаружил несколько недочетов, которые отправил в виде PR.
Ссылки
Explainshell.com - match command-line arguments to their help text
Conventions for Command Line Options
Testing - Command Line Applications in Rust
Some summaries on Rust string literals
Unique features of Rust - Training | Microsoft Learn
Small exercises to get you used to reading and writing Rust code!
The Little Book of Rust Macros
Writing Non-Trivial Macros in Rust · Michael-F-Bryan
Типы в языках программирования
Overview of the Compiler - Guide to Rustc Development
Use borrowed types for arguments - Rust Design Patterns
A Minimal Rust Kernel | Writing an OS in Rust
zesterer/chumsky: A parser library for humans with powerful error recovery.
rust-bakery/nom: Rust parser combinator framework
Прошлые записи
- Комната призвания
- Разбираемся с Coroutine в Kotlin - часть четвертая
- Разбираемся с Coroutine в Kotlin - часть третья
- Разбираемся с Coroutine в Kotlin - часть вторая
- Разбираемся с Coroutine в Kotlin - часть первая
- Отпуск длинною в год
- Подходит ли data class для JPA Entity?
- События как источник правды или как я в стартапе участвовал
- Код 2015 против 2023
- Jvm Internals - Перевод
- Мозг против живота или насколько трудно управлять своей жизнью