В этой статье хочу поделиться информацией о крейтах для языка rust, про которые вы могли не знать. Большинство из них позволяют писать меньше шаблонного кода или улучшают его читаемость. Эта статья будет полезна для разработчиков всех уровней, которые пишут на rust. Делитесь в комментариях крейтами, которые могут быть полезны другим разработчикам. Под крейтом (crate) в данной статье понимается библиотека, которую можно подключить как зависимость в ваш проект. Я не копировал всю документацию для каждого крейта в эту статью, поэтому если вас заинтересовал крейт, рекомендую ознакомиться с ним на crates.io или docs.rs. Также я не приводил в пример всеми известные крейты как serde, anyhow, itertools и другие. Вы могли заметить, что многие проекты, написанные на rust, имеют большое число первичных и вторичных зависимостей. По моему мнению, в этом нет ничего плохого и вот почему. Rust держит высокие требования к обратной совместимости стандартной библиотеки. Поэтому много функциональности предоставляется в виде third-party зависимостей, поддерживаемых сообществом, а не разработчиками языка. В то же время rust на этапе появления реализовал хорошее управление зависимостями в виде cargo, который делает использование зависимостей тривиальным. Вместе это позволяет крейтам rust развиваться быстрее, иметь меньше легаси, а также даёт пользователям возможность выбора между разными подходами к реализации функциональности, а не использования того, что дизайнеры языка заложили в стандартную библиотеку. Выше перечисленное позволяет писать крейты в стиле Unix way, когда каждая библиотека делает ровно одну вещь и делает её хорошо. Подход "batteries included", принятый в python, хорошо работал в 1990-е года, когда распространение ПО не было таким простым. Сейчас это приводит к инициативам по очистке стандартной библиотеки python. Крейт, позволяющий преобразовать цепочку вызовов функций из префиксной формы в суффиксную. Такая форма позволяет писать более читаемый код. Легче всего объяснить на примерах. Например, такая цепочка вызовов: Может быть переписана как: Или допустим вы хотите отсортировать массив "на месте" с помощью метода sort(), что потребует сначала сделать переменную мутабельной, а затем переопределить переменную, сделав её снова немутабельной: На помощь приходит метод .tap_mut, который передаёт в функцию-замыкание значение переменной по мутабельной ссылке: Соответственно, можно определить переменную collection лишь раз и сделав её сразу немутабельной. Данные методы не оказывают влияния на производительность кода, так как на этапе компиляции данные вызовы оптимизируются и результирующий код насколько же производителен как и "наивная" версия. По моему мнению в обоих примерах код стал более читаем, так как мы избавились от лишних объявлений переменных и переписали вызовы функций в chain-like вид, что позволяет читать код не прыгая глазами по строчкам. Это не все полезные методы, которые предоставляет данный крейт. Например, он содержит tap_x_dbg методы, которые работают в debug режиме и убираются в relese режиме. Также методы для конверсии между типами, реализующими Into трейт. Рекомендую ознакомиться с документацией данного крейта. Крейт помогающий избавиться от boilerplate кода при работе с енамами в расте. Функционал работает за счёт derive макросов. Пример функционала: strum::Display - реализует std::fmt::Display для енама и соответственно метод to_string() -> String. strum::AsRefStr - реализует AsRef<&static str>. Соответственно, не требует аллокации памяти при каждом вызове как при использовании to_string(). strum::IntoStaticStr - реализует From<MyEnum> for &'static str. Работает аналогично предыдущему варианту. strum::EnumString - реализует std::str::FromStr и std::convert::TryFrom<&str>, что позволяет преобразовывать строки в инстансы енама. strum::EnumCount - добавляет константу COUNT: usize, которая содержит количество вариантов енама. strum::EnumIter - реализует итератор по вариантам енама. Данные внутри вариантов будут установлены в Default::default(). И даже больше. Рекомендую заглянуть в документацию данного крейта. Пример использования макросов выше: Также разные макросы данного крейта поддерживают кастомизацию поведения. Например, можно менять строку, в которую будет преобразован инстанс анама атрибутом #[strum(serialize = "redred")]. В расте популярен паттер под названием NewType. Суть заключается в "оборачивании" сторонних библиотечных типов в нашу структуру: Одним из примеров использования данного паттерна является сохранение каких-то инвариантов. Для нашей структуры мы можем определить функцию-конструктор, которая будет проверять, что внутренний вектор всегда не пустой: Соответсвенно, структуру можно создать только через конструктор, который проверяет требуемый инвариант. Минусом данного подхода является то, что для наша структура-враппер NonEmptyVec не наследует имплементацию трейтов внутреннего типа. Например, что если мы захотим передать структуру в функцию, которая принимает на вход IntoIterator? Этот код не скомпилируется: Мы можем написать свою реализацию: Но по сути это boilerplate код, потому что это дублирование уже существующей реализации трейта внутреннего типа. Избавиться от такого кода поможет данный крейт. Просто добавляем использование derive макросов для нашей структуры-враппера: И проверяем, что требуемый функционал работает: Как видите, мы автоматически восстановили реализацию сразу нескольких полезных трейтов. Данный крейт содержит макросы для генерации трейтов конвертации (From, IntoIterator, AsRef, etc), форматирования (Display-like), операторов (Add, Index, etc), полезных методов (Constructor, Unwap, etc). Одним из популярных паттернов в расте является builder [1, 2]. Данный паттерн удобен, когда нужно создать сложную структуру со множеством полей. Например, предположим у нас есть несколько функций, проделывающих сложную работу и возвращающих результат Calculation - структуру со множеством опциональных полей - а затем мы хотим проверить работу функций в юнит тесте: Получилось довольно многословно. Давайте используем derive_builder: Крейт сгенерировал билдер структуру с именем CalculationBuilder и сеттерами для каждого поля. Теперь тест можно переписать намного короче: Как видите, теперь мы можем присвоить значения только требуемым полям и тест получился намного короче. Если бы опциональных полей в структуре было больше, то и выигрыш в краткости и читаемости был выше. Это только один из примеров использования builder паттерна, который облегчает данный крейт. Также крейт поддерживает валидацию полей билдера в build методе и несколько видов билдеров (owned, mutable, immutable). Пожалуй единственный минус этого крейта, на мой взгляд, является то, что генерируемый build метод всегда возвращает Result<T>, даже когда T состоит только из опциональных полей, как в нашем случае. Библиотека для снапшот тестирования. Снапшот - ожидамый результат теста, обычно хранимый в отдельном файле. Библиотека предоставляет command line утилиту для удобного обновления снапшотов. Крейт предоставляет множество фич, можно с ними ознакомиться в официальном гайде. Одна из полезных фич, по моему мнению, является redactions. Она позволяет тестировать значения с рандомными полями или с недерминированным порядком, например HashSet: Для данного теста был автоматически сформирован снапшот snapshots/insta__tests__redactions.snap с данным содержанием: Раст поддерживает полиморфизм с помощью статической и динамической диспетчиризации трейтов. Если вы использовали динамическую диспатчеризацию, то знаете, что она может негативно сказываться на производительности программы, так как реализация трейта ищется через vtable во время выполнения программы. Данный трейт позволяет превратить динамическую диспетчиризацию в статическую с использованием енама. Предположим, у нас есть такой трейт и его реализации: В этом примере мы используем динамическую диспетчиризацию: Теперь используем данный трейт: Таким образом крейт сгенерировал за нас реализацию трейта ReturnsValue для енама EnumDispatched. Авторы крейта произвели замеры производительности и выяснили, что такая реализация может ускорить использование трейта до 10-12 раз. На мой взгляд основный минус данной библиотеки в том, что можно сгенерировать реализацию только трейта, объявленного в вашем крейте. Так как необходимо применить макрос #[enum_dispatch::enum_dispatch] непосредственно на трейт (чтобы enum_dispatch смог прочитать сигнатуры функций трейта). Соответственно применить макрос к трейту можно только в вашем крейте, который вы можете редактировать. Данный крейт позволяет конкатенировать идентификаторы во время компиляции без использования nightly. Это полезно при написании макросов, чтобы создавать произвольные идентификаторы, используя переменные макроса и статические идентификаторы-литералы. Сокращённый пример из readme крейта. Данный макрос создаст impl блок для типа с именем $name и создаст методы-геттеры для каждого $field. Енам общего назначения Either с двумя вариантами Left и Right. Реализует множество методов и трейтов для удобной работы с данным енамом. Коллекция числовых трейтов и типов. Включает в себя дженерики для чисел, большие числа (big integers), комплексные числа и тд. Крейт предоставляет макрос для реализации std::error::Error трейта на структуры и енамы. С точки зрения обработки ошибок есть 2 типа крейтов: библиотеки и приложения. Библиотека создаётся как third-party зависимость, которая будет использоваться в приложениях. Для крейтов-библиотек важно, чтобы вызывающий код мог при необходимости проверить, какого типа возникла ошибка в коде библиотеки, и реализовать разное поведения для разных видов ошибок. Например, игнорировать ошибки ввода-вывода, а на ошибках формата данных вызывать панику. Для приложений обычно не важен конкретный тип ошибок, поэтому функции приложений обычно возвращают тип Result<T, anyhow::Error>, так как anyhow позволяет удобно конвертировать любые ошибки в anyhow::Error тип с помощью ? оператора или From трейта. Подробнее тут:  (немного старая статья) и  (более новая). Данный крейт в основном используется для удобной реализации ошибок в крейтах-библиотеках. Пример использования: В примере выше ошибка std::num::ParseIntError была сконвертирована в SomeError::ParseInt енам. Без данного крейта нам пришлось бы прописывать все преобразования вручную. Облегчает использование параллелизма в расте. Подходит для превращения последовательных итераторов в параллельные. Гарантирует отсутствие гонки данных (data-race). Параллельные итераторы в рантайме адаптируют своё поведение для максимальной производительности. В примере выше последовательный итератор был превращён в параллельным всего лишь изменением iter() в par_iter(). Крейт предоставляет набор инструментов для конкурентного программирования: атомики, структуры данных, управление памятью, синхронизация тредов и тд. Например, реализация каналов (channels) данного крейта более производительная по сравнению с каналами std (по заверению разработчиков) и позволяет иметь несколько записывающих и несколько читающих потоков (multi-producer multi-consumer) в отлиии от std каналов, которые разрешают только один читающий поток (multi-producer single-consumer). Крейт позволяет определять трейты с асинхронными функциями. На данный момент раст не поддерживает асинхронные трейты на уровне синтаксиса языка, поэтому вы можете использовать одноимённый макрос из этого крейта. Подробнее почему здесь. Макрос изменяет сигнатуры функций, чтобы они возвращали Pin<Box<dyn Future + Send + 'async_trait>>. Данный крейт содержит функции-врапперы с человекочитаемыми ошибками для функций из std::fs. Если вы использовали функции из std::fs (такие как read_to_string или write) возможно вы замечали, что в случае ошибки сообщение будет не очень полезным: Из данного сообщения не понятно, какой из файлов не существует. Но если мы используем fs-err крейт, то мы получим более подробное сообщение: Крейт для создания временных файлов и директорий. Енкодер и декодер структур в массив байтов и обратно. Использует компактный формат данных, подходящий для хранения на диске и для обмена данными между системами с разными архитектурами процессора. Макросы для генерации типов-контейнеров из std. По большей части дело вкуса, ведь контейнеры имеют from и from_iter методы. Есть открытый RFC на эту тему. Хэш-таблица, которая сохраняет порядок вставки элементов в неё. То есть итерация по элементам хэш-таблицы будет происходить в том же порядке, что и порядок вставки элементов. Данный порядок сохраняется до тех пор, пока вы не вызываете метод remove. Поддерживает поиск элементов по ключу и по числовым индексам (как массив) и быструю итерацию по элементам. Все эти свойства исходят из того, что внутри хранится вектор пар ключ-значение и хэш-таблица от хэшей ключей к их индексам в векторе. Стоит использовать, если данные свойства подходят для вашей задачи. Данный крейт понравится бывшим Java программистам. Крейт содержит процедурные макросы для генерации методов геттеров и сеттеров. Библиотека для автоматического создания mock объектов для (почти всех) трейтов и структур. Данные объекты могут использоваться в юнит-тестах вместо объектов оригинального типа, что может облегчить написание высокоуровневых юнит тестов или протестировать сложнопроверяемые крайние случаи. Генерировать mock объекты можно автоматиески с помощью attribute-макроса #[automock]. Но он имеет свои ограничения, поэтому иногда приходиться использовать процедурный макрос mock! с более ручной реализацией mock объектов. Фреймворк для тестирования на основе свойств. Позволяет тестировать код на большом количестве произвольных входных данных. При нахождении ошибки автоматически находит минимальный тест-кейс для воспроизведения ошибки. Также как и quickcheck является фреймворком для тестирования на основе свойств. Но обладает более гибкой генерацией входных данных по сравнению с quickcheck, хотя для сложных данных может работать сильно дольше чем quickcheck. Библиотека для конвертации текста в разные общепринятые стили написания идентификаторов переменных, такие как CamelCase, snake_case и другие. Например, когда вы используете атрибут rename_all в библиотеке sqlx, то внутри используется функционал heck. Небольшой крейт, помогающий определить количество физических ядер процессора или количество параллельных задач, которые могут эффективно выполняться на данной системе. Например, я его использовал для определения количества тредов при прохождении туториала Ray Tracing in One Weekend. Библиотека предоставляет форматтер и парсер для std::time::{Duration, SystemTime} в человекочитаемом виде. Также имеет интеграцию с serde с помощью крейта humantime-serde. Это позволяет, например, указывать значения Duration в конфиге приложения/сервиса в удобочитаемом виде вместо использования единиц измерения в имени переменной, что уменьшает вероятность ошибки: Пример использования: Предоставляет макрос для облегчения реализации трейтов-операторов. Данный крейт подойдёт, когда нужна более хитрая реализация оператора: в примере ниже мы сгенерировали сложение двух разных типов, которое на самом деле работает как a * b + 1. Для простых случаев подойдёт крейт derive_more. Макросы для генерации итераторов по значениям енама или структуры. Позволяет удобно объявлять элементы, зависящие от большого числа #[cfg], в виде if-else выражений. Макросы для удобного создания массивов (array) из слайсов. Предоставляет процедурные макросы для более быстрой, гибкой и декларативной реализации трейтов из стандартной библиотеки. В частности Debug, Eq, Ord, Deref и тд. Гибкость заключается в возможности исключать поля из реализации, включения trait bounds и тд. Также как и educe предоставляет макросы для реализации трейтов стандартной библиотеки. Но последнее обновление библиотеки было в январе 2021 года. В rust для изменения даты используется тип Duration, который представляет из себя фиксированное количество секунд и наносекунд. Так chrono::Duration имеет функции-конструкторы с именами weeks, days, hours, но не имеет конструктора month, потому что это относительная величина, которую нельзя выразить в секундах. Поэтому если вы хотите изменить дату на более привычную для человеческого понимания величину, например прибавить месяц или год, то можете использовать инструменты данного крейта. https://www.reddit.com/r/rust/comments/uevmnx/what_crates_would_you_consider_essential/ https://www.reddit.com/r/rust/comments/ylp4nz/what_crates_are_considered_as_defacto_standard/ https://blessed.rs/crates https://lib.rs/ https://crates.io/crates?sort=recent-downloads https://www.reddit.com/r/rust/comments/nuq1ix/whats_your_favourite_underrated_rust_crate_and_why/