Files
crank/docs/rust-code-rules.md
T
2026-03-25 12:20:42 +03:00

317 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Rust Code Rules
## 1. Назначение документа
Этот документ фиксирует Rust-specific правила кода для проекта:
- toolchain;
- linting;
- formatting;
- ошибки;
- async;
- ownership;
- visibility;
- dependency hygiene.
Цель документа - убрать плавающие договоренности по стилю и практике командной Rust-разработки.
## 2. Toolchain
### 2.1. Версия Rust
Для проекта должен быть зафиксирован `rust-toolchain.toml`.
В нем должны быть определены:
- стабильный `channel`;
- `edition`;
- при необходимости `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<Mutex<_>>` как универсальный контейнер состояния;
- 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;
- код читается за счет имен и декомпозиции, а не за счет комментариев.