9.4 KiB
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.
Рекомендуемый состав:
rustfmtclippy
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 --checkcargo clippy --all-targets --all-features -- -D warningscargo test
Опционально позже:
cargo denycargo 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<Mutex<_>>как универсальный контейнер состояния; - shared mutability допускается только там, где она действительно нужна по архитектуре.
8.3. ID types
Идентификаторы должны быть отдельными типами, а не просто String.
Примеры:
OperationIdDescriptorIdAuthProfileId
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;- код читается за счет имен и декомпозиции, а не за счет комментариев.