# Rust Code Rules ## 1. Назначение документа Этот документ фиксирует Rust-specific правила кода для проекта: - toolchain; - linting; - formatting; - ошибки; - async; - ownership; - visibility; - dependency hygiene. Цель документа - убрать плавающие договоренности по стилю и практике командной Rust-разработки. ## 2. Toolchain ### 2.1. Версия Rust Для проекта должен быть зафиксирован `rust-toolchain.toml`. В нем должны быть определены: - стабильный `channel`; - `protocol`; - при необходимости `components`. Рекомендуемый состав: - `rustfmt` - `clippy` ### 2.2. MSRV Нужно зафиксировать `MSRV` - минимально поддерживаемую версию Rust. Правило: - без необходимости не использовать возможности языка новее зафиксированного `MSRV`; - обновление `MSRV` - это отдельное осознанное решение. ## 3. Formatting и linting ### 3.1. Formatting Обязательное правило: - весь код форматируется через `cargo fmt`. Ручной стиль форматирования не обсуждается и не поддерживается. ### 3.2. Clippy Обязательное правило: - `cargo clippy --all-targets --all-features -- -D warnings` Предупреждения считаются ошибками, если нет явно зафиксированного исключения. ### 3.3. CI quality gates Минимально в CI должны запускаться: - `cargo fmt --check` - `cargo clippy --all-targets --all-features -- -D warnings` - `cargo test` Опционально позже: - `cargo deny` - `cargo audit` ## 4. `unsafe` Для проекта принимается правило: - `unsafe` запрещен по умолчанию. Если когда-либо потребуется `unsafe`, то: - это должно быть отдельное осознанное решение; - причина должна быть технически обоснована; - блок должен быть минимальным; - вокруг него должны быть тесты. Для MVP можно считать: - `unsafe_code = deny` ## 5. Panic policy В production code запрещены: - `unwrap()` - `expect()` - `todo!()` - `unimplemented!()` - `dbg!()` - необоснованные `panic!()` Допускается: - в тестах; - в очень раннем bootstrap-коде, если это действительно аварийное завершение и не часть доменной логики. Базовое правило: - ошибки возвращаются через `Result`, а не через panic. ## 6. Правила ошибок ### 6.1. Domain и service errors В домене и сервисах использовать типизированные ошибки. Рекомендуемо: - `thiserror` ### 6.2. Application boundary На верхних слоях приложений допускается агрегирование ошибок, если это упрощает wiring. При необходимости: - `anyhow` только на внешних границах приложения, не в доменной модели. ### 6.3. Error context Ошибка должна сохранять стадию отказа: - schema - mapping - adapter - persistence - external service - internal runtime ## 7. Visibility rules Правило: - по умолчанию все приватное; - `pub(crate)` предпочтительнее `pub`; - публичный API должен быть минимальным. Нельзя: - открывать модуль наружу "на всякий случай"; - делать `pub` просто ради удобства из соседнего файла; - реэкспортировать целые деревья модулей без причины. ## 8. Ownership и данные ### 8.1. Клонирование Правило: - не клонировать данные без необходимости; - клон должен быть осознанным, а не способом обойти borrow checker без понимания причины. ### 8.2. Shared mutability Правило: - не использовать `Arc>` как универсальный контейнер состояния; - shared mutability допускается только там, где она действительно нужна по архитектуре. ### 8.3. ID types Идентификаторы должны быть отдельными типами, а не просто `String`. Примеры: - `OperationId` - `DescriptorId` - `AuthProfileId` ## 9. Async rules ### 9.1. Где допускается `async` `async` используется только там, где есть: - I/O; - network; - storage; - async boundary приложения. ### 9.2. Где `async` не нужен Нельзя превращать: - schema validation; - mapping; - чистую доменную логику; - небольшие derived methods в `async fn` без причины. ### 9.3. `tokio` `tokio` должен находиться: - в приложениях; - в I/O слоях; - в адаптерах и runtime orchestration, если там есть реальный async. Доменный слой не должен зависеть от `tokio`. ## 10. API design rules ### 10.1. Конструкторы Использовать: - `new()` для гарантированно валидного и простого создания; - `try_new()` там, где есть валидация и возможна ошибка. ### 10.2. Builders Если структура имеет много параметров и прямой конструктор становится нечитаемым, допускается builder. Но: - builder не должен маскировать плохую модель данных; - builder не должен использоваться как замена нормальной декомпозиции. ### 10.3. DTO отдельно от domain Если HTTP payload начинает расходиться с доменной моделью, нужно вводить отдельный DTO слой. Нельзя: - тащить `serde`-ориентированный API payload прямо в домен только ради удобства. ## 11. Dependency rules ### 11.1. Внешние crates Правило: - сначала использовать `std`; - потом существующие внутренние abstractions; - только потом тянуть новый внешний crate. Нельзя: - добавлять зависимость "на всякий случай"; - дублировать crates с пересекающейся функцией без причины. ### 11.2. Макросы Правило: - не злоупотреблять макросами там, где обычный Rust-код читается лучше; - derive-макросы допустимы; - сложные процедурные макросы без сильной причины не нужны. ## 12. Serialization rules ### 12.1. JSON/YAML Правило: - доменная модель одна; - `JSON` и `YAML` - только два формата сериализации; - нельзя допускать, чтобы YAML export стал отдельной несовместимой моделью. ### 12.2. Secrets Никогда не сериализовать: - реальные токены; - пароли; - API keys в exports, logs и test snapshots. ## 13. Тестовые практики на уровне Rust-кода Минимально: - unit tests рядом с модулем или в `tests`; - integration tests для crate boundaries; - фикстуры для schema/mapping/proto/yaml roundtrip. Полезное правило: - баг сначала воспроизводится тестом, потом фиксится кодом. ## 14. Что часто запрещают в Rust-командах Практически всегда под запретом: - `unwrap()` в production code; - `unsafe` без review; - giant modules; - giant enums со всем подряд; - giant services, где смешаны orchestration и transport; - абстракции "на будущее" без второго реального кейса. ## 15. Практический итог Для этого проекта правильный Rust-профиль такой: - фиксированный toolchain; - обязательные `fmt` и `clippy`; - `unsafe` запрещен по умолчанию; - panics запрещены в production code; - ошибки типизированы; - `pub` минимизируется; - `async` только на реальных async boundaries; - код читается за счет имен и декомпозиции, а не за счет комментариев.