126 lines
13 KiB
Markdown
126 lines
13 KiB
Markdown
<!-- file: docs/rules/RULES_RUST.md -->
|
|
<!-- version: 7 -->
|
|
|
|
# Règles Rust générales
|
|
|
|
## Portée
|
|
|
|
Les règles `RUST-*` s'appliquent à tous les fichiers Rust de KSP : crates, sources, tests, exemples et outils. Elles reprennent les règles déjà éprouvées sur les générations précédentes du projet et les rendent explicites afin que `rustfmt` et Clippy ne soient jamais considérés comme des contrôles structurels suffisants.
|
|
|
|
## Édition et lints
|
|
|
|
- **RUST-BASE-001** — L'édition Rust est Rust 2024, sauf contrainte externe explicitement documentée.
|
|
- **RUST-BASE-002** — Chaque `lib.rs` et `main.rs` contient `#![warn(missing_docs)]`, `#![deny(unreachable_pub)]` et `#![forbid(unsafe_code)]`.
|
|
- **RUST-BASE-003** — Les lints communs sont déclarés au niveau workspace et hérités par les crates.
|
|
- **RUST-BASE-004** — Le code `unsafe` est interdit.
|
|
- **RUST-BASE-005** — Tout fichier Rust possède les en-têtes `// file: ...` et `// version: N`, et se termine par exactement une fin de ligne.
|
|
|
|
## Documentation des API et contrats internes
|
|
|
|
- **RUST-DOC-001** — Tout élément `pub` ou `pub(crate)` possède une rustdoc utile au point de déclaration. Cette règle couvre aussi les méthodes associées et les champs nommés visibles à ces niveaux.
|
|
- **RUST-DOC-002** — Toute réexportation `pub use` ou `pub(crate) use` dans `lib.rs` ou `main.rs` possède sa propre rustdoc utile et adjacente.
|
|
- **RUST-DOC-003** — La façade d'une crate doit permettre de comprendre son API publique et ses contrats crate-wide sans dépendre de chemins de modules internes.
|
|
- **RUST-DOC-004** — Les rustdocs et commentaires du code sont en anglais ; la documentation Markdown KSP reste en français sauf document explicitement destiné à une audience différente.
|
|
|
|
## Imports et chemins
|
|
|
|
- **RUST-IMPORT-001** — `use` est interdit pour les constantes, fonctions, structures, énumérations, unions, alias de types, modules et macros.
|
|
- **RUST-IMPORT-002** — `use` est autorisé uniquement pour un trait lorsqu'une résolution de méthode, une dérivation ou une contrainte du langage l'exige réellement. Le motif est explicité sur la ligne par `rust-rules: trait-import`.
|
|
- **RUST-IMPORT-003** — Les imports de traits restent étroits ; un import de trait ne mélange jamais d'éléments non-traits.
|
|
- **RUST-IMPORT-004** — Les glob imports et imports groupés par accolades sont interdits.
|
|
- **RUST-IMPORT-005** — Les alias `as` sont interdits dans tous les `use`, réexports et déclarations `extern crate`. Le symbole reçoit son nom canonique dans son module propriétaire.
|
|
- **RUST-IMPORT-006** — Les déclarations `use` sont au niveau du module, dans le bloc d'en-tête avant les déclarations métier. Aucun `use` n'est introduit dans une fonction, méthode, bloc, test ou branche conditionnelle.
|
|
- **RUST-IMPORT-007** — Pour un élément fourni par une crate externe, utiliser directement le chemin public le plus court exposé par cette crate ; ne pas créer un import non-trait pour raccourcir ce chemin.
|
|
- **RUST-IMPORT-008** — Pour un élément `pub` d'une crate KSP, la crate propriétaire le réexporte au crate-root. Une autre crate l'appelle via `owner_crate::Item`.
|
|
- **RUST-IMPORT-009** — Dans sa propre crate, un élément `pub` ou `pub(crate)` partagé est appelé via `crate::Item`, y compris depuis son module de déclaration lorsque le contrat est crate-wide.
|
|
- **RUST-IMPORT-010** — Un chemin `crate::module::Item` est interdit pour un élément partagé `pub`/`pub(crate)` qui peut être consommé via le crate-root. Le chemin du module interne n'est pas une façade.
|
|
- **RUST-IMPORT-011** — Un élément strictement privé à un module n'est pas réexporté et est appelé par son nom local dans ce module.
|
|
- **RUST-IMPORT-012** — Dans un sous-module de tests, `super::Item` est réservé à un élément strictement privé du module parent. Un élément `pub` ou `pub(crate)` continue d'être appelé via `crate::Item`.
|
|
- **RUST-IMPORT-013** — Les réexports internes commencent par `self::`. Un réexport d'une crate externe peut utiliser directement le chemin externe canonique.
|
|
- **RUST-IMPORT-014** — Un export correspond à une ligne de réexport distincte ; les accolades ne servent jamais à regrouper une façade.
|
|
- **RUST-IMPORT-015** — Une crate externe rendue publique uniquement pour l'hygiène d'une macro exportée conserve son nom canonique, porte `#[doc(hidden)]` et ne devient pas une API de consommation. `ksp-logging-lib::tracing` est ce bridge technique pour les macros Logging ; les autres crates KSP n'y accèdent jamais directement.
|
|
|
|
## Visibilité et façade de crate
|
|
|
|
- **RUST-API-001** — Aucun `pub mod` n'est autorisé ; les modules restent privés et l'API externe est constituée exclusivement par des réexports explicites au crate-root.
|
|
- **RUST-API-002** — `pub(in ...)` et `pub(super)` sont interdits.
|
|
- **RUST-API-003** — Un élément `pub` inaccessible depuis la façade de sa crate est une erreur de conception.
|
|
- **RUST-API-004** — Un élément `pub(crate)` consommé hors de son module est réexporté au crate-root via `pub(crate) use` puis appelé via `crate::Item`.
|
|
- **RUST-API-005** — Les chemins internes de modules ne constituent jamais une API stable.
|
|
- **RUST-API-006** — Si deux éléments crate-wide auraient le même nom au crate-root, ils sont renommés dans leurs modules propriétaires avec des noms canoniques non ambigus ; un alias de réexport n'est pas utilisé pour masquer la collision.
|
|
|
|
## Formatage, blocs et ordre
|
|
|
|
- **RUST-FMT-001** — `rustfmt.toml` à la racine est la configuration canonique du formatage Rust.
|
|
- **RUST-FMT-002** — `cargo fmt --all` est exécuté après chaque modification Rust avant l'audit structurel et les validations Cargo.
|
|
- **RUST-FMT-003** — Aucune ligne vide n'est conservée à l'intérieur du corps d'une fonction ou méthode, ni à l'intérieur d'une définition `struct` ou `enum`.
|
|
- **RUST-FMT-004** — Deux fonctions ou méthodes distinctes, y compris deux `const fn`, sont séparées par exactement une ligne vide. La même séparation exacte s'applique entre `struct`, `enum`, `union`, `trait`, bloc `impl` et fonction/méthode lorsqu'ils constituent des items voisins d'un même scope.
|
|
- **RUST-FMT-005** — Les blocs homogènes sont ordonnés alphabétiquement par nom lorsque l'ordre n'a pas de signification sémantique.
|
|
- **RUST-FMT-006** — Les constantes de module ou associées sont ordonnées par visibilité `pub`, puis `pub(crate)`, puis privée, et alphabétiquement dans chaque niveau. Deux constantes de même visibilité appartenant au même bloc ne sont séparées par aucune ligne vide ; exactement une ligne vide sépare deux niveaux de visibilité distincts.
|
|
- **RUST-FMT-007** — Dans une façade, le bloc `pub use` précède le bloc `pub(crate) use`; une seule ligne vide sépare les deux blocs, aucune ligne vide n'existe à l'intérieur d'un bloc, et les symboles sont ordonnés alphabétiquement.
|
|
- **RUST-FMT-008** — Les déclarations `mod` d'un même bloc sont ordonnées alphabétiquement ; les modules de tests conditionnels restent après les modules de production.
|
|
- **RUST-FMT-009** — Lorsqu'un `struct` et ses blocs `impl` forment une unité locale sans contrainte de séparation, l'implémentation suit la structure. Les éléments visibles précèdent les helpers privés lorsqu'aucun ordre métier ou protocolaire n'impose l'inverse.
|
|
- **RUST-FMT-010** — L'ordre alphabétique n'écrase jamais un ordre contractuel ou sémantique : wire fields, comptes Solana, étapes de protocole, priorités, transitions d'état, tableaux de dispatch et séquences explicitement normatives conservent leur ordre défini.
|
|
- **RUST-FMT-011** — Les blocs homogènes de `type` ou `static` de même visibilité suivent la même règle d'espacement compact que les constantes ; un changement de catégorie ou de visibilité recrée une séparation d'exactement une ligne vide.
|
|
- **RUST-FMT-012** — Une déclaration d'item placée après un `return` inconditionnel au niveau principal d'une fonction/méthode est interdite ; elle est traitée comme un signal probable d'accolade fermante déplacée ou de bloc mal restructuré.
|
|
|
|
## Contrôle de flux et erreurs
|
|
|
|
- **RUST-ERR-001** — `unwrap`, `expect` et `panic` sont interdits dans le code de production.
|
|
- **RUST-ERR-002** — L'opérateur `?` est interdit dans le code de production ; les chemins d'erreur utilisent un contrôle de flux explicite.
|
|
- **RUST-ERR-003** — Les retours sont explicites conformément au lint `clippy::implicit_return`.
|
|
- **RUST-ERR-004** — `anyhow` et `thiserror` ne sont pas utilisés par défaut ; leur introduction exige une justification architecturale.
|
|
- **RUST-ERR-005** — Les erreurs publiques sont typées lorsque leur contrat est stable.
|
|
- **RUST-ERR-006** — Les tests peuvent utiliser `unwrap` ou `expect` uniquement dans la limite explicitement autorisée par la configuration Clippy.
|
|
- **RUST-ERR-007** — Avant d'attacher une erreur externe comme `source` de `ksp_core_lib::Error`, la crate propriétaire vérifie que sa chaîne `Debug`/`source` ne peut pas exposer de secret. Lorsqu'une dépendance fournit une primitive de neutralisation, elle est appliquée avant `with_source`.
|
|
|
|
## Helpers et duplication
|
|
|
|
- **RUST-HELP-001** — Un helper répété dans plusieurs modules d'une même crate est déplacé dans un module commun dont le nom décrit la responsabilité la plus précise possible.
|
|
- **RUST-HELP-002** — Un helper réellement général et réutilisable par plusieurs crates est déplacé dans la bibliothèque KSP appropriée.
|
|
- **RUST-HELP-003** — La mutualisation ne doit pas créer de dépendance cyclique ni déplacer une logique métier spécifique dans une bibliothèque générique.
|
|
|
|
## Dépendances
|
|
|
|
- **RUST-DEP-001** — Une dépendance n'est ajoutée que si elle est réellement utilisée dans le chemin de compilation concerné.
|
|
- **RUST-DEP-002** — Une dépendance uniquement utilisée par les tests reste dans `[dev-dependencies]`.
|
|
- **RUST-DEP-003** — Les features sont minimales et explicites.
|
|
- **RUST-DEP-004** — Les lockfiles de dépendances ne sont pas versionnés dans KSP.
|
|
- **RUST-DEP-005** — KSP privilégie les versions récentes compatibles. Toute version volontairement contrainte ou ancienne est documentée avec sa raison et la condition permettant de lever la contrainte.
|
|
|
|
## Assertions et organisation des tests
|
|
|
|
- **RUST-TEST-001** — Un test vérifiant uniquement l'appartenance à un ensemble peut trier les valeurs obtenues et attendues avant `assert_eq!` lorsque l'ordre n'est pas contractuel.
|
|
- **RUST-TEST-002** — Une collection n'est jamais triée artificiellement lorsque l'ordre fait partie du contrat, notamment pour comptes Solana, metas, instructions, signers, événements, étapes de pipeline, priorités ou journaux ordonnés.
|
|
- **RUST-TEST-003** — Les tests unitaires sont placés autant que possible dans `unit_tests/` et reflètent l'arborescence de `src/`.
|
|
- **RUST-TEST-004** — Un fichier `unit_tests/...` est rattaché explicitement au module de production via `#[cfg(test)]` et `#[path = ...]`; il peut tester les éléments privés de ce module.
|
|
- **RUST-TEST-005** — `tests/` est réservé aux vrais tests d'intégration consommant uniquement l'API publique de la crate.
|
|
- **RUST-TEST-006** — Toute partie pertinente de l'API publique est couverte, lorsque possible, par un test d'intégration afin de valider les réexports et la visibilité réellement consommables.
|
|
- **RUST-TEST-007** — La colocalisation d'un test unitaire dans le fichier de production reste une exception justifiée.
|
|
|
|
## Audit structurel automatisé
|
|
|
|
- **RUST-AUDIT-001** — `scripts/audit_rust_workspace_rules.py` est le point d'entrée obligatoire de l'audit Rust KSP. Il exécute les audits généraux, la complétude des réexports/chemins et les frontières KSP sans fusionner leurs responsabilités.
|
|
- **RUST-AUDIT-002** — L'audit est dependency-free côté Python standard et échoue avec un code non nul dès qu'une violation mécanique est détectée.
|
|
- **RUST-AUDIT-003** — L'audit contrôle au minimum : headers/version/newline, lints de crate-root, visibilité interdite, `use`/aliases/groupes/globs/scope, rustdocs visibles, ordre des imports/réexports/constantes, lignes vides dans fonctions/structs/enums, complétude des réexports crate-root, chemins `crate::module::Item`, usage de `super::` dans les tests séparés et frontières KSP directement vérifiables.
|
|
- **RUST-AUDIT-004** — Les règles contextuelles qui ne peuvent pas être prouvées sans interpréter la sémantique restent des critères de revue humaine ; le script ne doit pas produire de faux sentiment de complétude.
|
|
|
|
## Contrôle avant livraison
|
|
|
|
Après toute modification Rust, la séquence minimale est :
|
|
|
|
```bash
|
|
cargo fmt --all
|
|
python3 scripts/audit_rust_workspace_rules.py
|
|
cargo check --workspace
|
|
cargo clippy --workspace --all-targets
|
|
```
|
|
|
|
Pendant le développement, des tests ciblés suivent ce gate. À la fermeture d'une prerelease technique/release, exécuter également :
|
|
|
|
```bash
|
|
cargo test --workspace
|
|
```
|
|
|
|
Une commande non exécutée n'est jamais déclarée réussie. `rustfmt`, `cargo check` et Clippy ne remplacent pas l'audit structurel KSP.
|