# 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;
- код читается за счет имен и декомпозиции, а не за счет комментариев.
