Files
crank/docs/rust-code-rules.md
T
github-ops ba29ac7b94
Deploy / deploy (push) Successful in 2m44s
CI / Rust Checks (push) Successful in 5m31s
CI / UI Checks (push) Successful in 5s
CI / Deployment Manifests (push) Successful in 2s
CI / Frontend E2E (push) Successful in 4m24s
chore: publish clean community baseline
2026-06-19 16:45:51 +00:00

9.4 KiB
Raw Blame History

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