7.8 KiB
Audit facultatif de façade Rust et canonicalisation intra-crate
Responsabilité
scripts/audit_rust_export_completeness.py contrôle un contrat Rust optionnel plus strict que les garanties du compilateur et de Clippy : une crate qui adopte cette convention possède une façade crate-root explicite pour ses items partagés, et toute consommation intra-crate d'un item possédant cette identité canonique passe par crate::Item.
Le propriétaire normatif reste ../rules/510-RUST_CARGO.md. Le script ne définit pas une architecture à lui seul ; il matérialise uniquement le contrat strict lorsqu'un projet choisit de l'adopter.
L'audit émet deux familles de diagnostics :
RUST-API-201
item `pub` d'un module fichier sans réexport explicite au crate-root,
ou item `pub(crate)` clairement consommé hors de son module sans réexport crate-root
RUST-IMPORT-201
référence intra-crate non canonique à un item possédant une identité crate-root ;
la forme attendue est `crate::Item`
RUST-IMPORT-201 ne se limite donc pas à crate::module::Item. Il couvre notamment :
Item
self::Item
super::Item
module::Item
crate::module::Item
lorsque ces formes désignent un item partagé déjà exposé comme crate::Item.
La règle s'applique également dans le module qui déclare l'item. Par exemple :
pub struct Runtime;
impl Runtime {
pub fn new() -> Runtime {
return Runtime;
}
}
n'est pas canonique après réexport de Runtime. Le contrat strict attend :
pub struct Runtime;
impl crate::Runtime {
pub fn new() -> crate::Runtime {
return crate::Runtime;
}
}
Le même principe vaut pour une fonction partagée appelée depuis son module propriétaire : crate::build() est canonique, build() ne l'est pas.
Les lignes de déclaration de l'item et les lignes de réexport qui construisent la façade sont naturellement exemptées. Un item strictement privé n'appartient pas à ce contrat ; dans un test unitaire attaché à son module parent, super::PrivateItem reste une forme locale légitime.
Pourquoi Cargo/Clippy ne suffisent pas
Le compilateur vérifie la résolution et la visibilité des noms ; il ne possède pas la convention « tout accès partagé passe par crate::Item ». Clippy peut signaler certains imports ou réexports inutilisés, mais une seule consommation correcte de crate::Item peut suffire à rendre le réexport utilisé tandis que d'autres références continuent d'employer un nom nu ou un chemin interne.
L'audit fournit donc une preuve distincte : uniformité de la façade et des chemins intra-crate, pas simple compilabilité.
Cibles Cargo et graphes de modules
La version: 2 ne confond plus automatiquement tous les fichiers d'un package Cargo dans une seule crate. Un package peut contenir simultanément une bibliothèque src/lib.rs et un binaire src/main.rs ; ces deux cibles sont compilées comme des crates distinctes et crate::Item n'y désigne pas la même racine.
L'audit découvre les packages récursivement puis traite séparément les cibles standard/explicites lib et bin. Pour chaque cible, il suit les modules fichiers déclarés par mod name; et les modules #[path = "..."], ce qui couvre notamment les unit_tests/ attachés utilisés dans le corpus KSP/Games sans englober les tests d'intégration Cargo sous tests/, qui sont des crates externes.
Cette distinction évite de considérer à tort owner_crate::Item utilisé dans un binaire comme une violation de la bibliothèque du même package.
Provenance et correction du corpus
KSP 0.3.16 et Games 0.3.5 contiennent un audit_rust_export_completeness.py historiquement identique à l'exception de sa révision. Cette convergence justifie l'extraction du concept, mais pas une copie littérale.
Deux limites historiques ont été identifiées :
- la découverte
crates/*/Cargo.tomlcouvrait les22packages KSP directement souscrates/, mais aucune des23crates Games imbriquées souscrates/apps/,crates/common/,crates/engines/, etc. ; - la canonicalisation détectait surtout
crate::module::Itemet quelques cas particuliers de tests, mais pas les références ordinaires par nom nu dans les modules de production, y compris le module propriétaire.
La première version PGR de alpha.5 corrigeait seulement la découverte récursive et le chemin interne long. La revue opérateur après sa gate a explicitement révélé la seconde lacune ; alpha.5 est donc conservé FAIL et alpha.5.fix.1 étend RUST-IMPORT-201 au contrat réellement attendu.
Mesure de réconciliation après le fix
Avec la version: 2, en mode --report-only --summary-only, les baselines autoritaires de la réconciliation donnent :
KSP 0.3.16
22 package(s)
1525 candidate(s)
RUST-IMPORT-201: 1525
Games 0.3.5
23 package(s)
181 candidate(s)
RUST-API-201: 2
RUST-IMPORT-201: 179
Ces nombres représentent des lignes candidates, pas des déclarations distinctes. Ils sont beaucoup plus élevés que la première mesure 0 / 2 parce que le contrat corrige précisément le blind spot signalé : les références nues déjà répandues dans les modules propriétaires et consumers deviennent enfin visibles.
KSP et Games n'étant pas encore gouvernés par cette version de PGR, ces résultats ne sont pas qualifiés de défauts rétroactifs et ne déclenchent aucune migration dans 0.0.5. Ils constituent un inventaire d'adoption future. Pour un projet existant, l'usage initial en --report-only est donc particulièrement important.
Adoption
Pour adopter cette convention dans un projet Rust, copier :
scripts/_rust_audit_common.py
scripts/audit_rust_export_completeness.py
puis adapter les headers de fichiers au dépôt destinataire.
Commande canonique :
python3 -B scripts/audit_rust_export_completeness.py --root .
Le code de sortie est 0 lorsque le corpus est propre, 1 lorsqu'au moins un candidat est trouvé et 2 lorsque la racine est invalide, illisible ou ne contient aucun package Rust détectable. --report-only permet un inventaire sans bloquer la gate ; --summary-only réduit la sortie aux comptes.
Une adoption progressive d'un projet existant devrait commencer par :
python3 -B scripts/audit_rust_export_completeness.py --root . --report-only --summary-only
puis inventorier/corriger les familles d'écarts avant de rendre l'audit bloquant. Une nouvelle crate créée sous le contrat peut au contraire l'activer immédiatement.
Limites
L'analyse reste lexicale et ne remplace pas un parseur Rust complet. Elle vise les déclarations module-level pub/pub(crate), les réexports explicites et les références dans le graphe de modules d'une cible. Les macros générant des items, modules inline complexes, include!, formes de #[path] atypiques et réexports élaborés peuvent nécessiter une revue humaine.
Pour réduire les faux positifs, une fonction partagée est reconnue comme référence lorsqu'elle apparaît sous une forme d'appel directe ; l'usage d'une fonction comme valeur de premier ordre peut donc échapper à cette version. Les chemins explicitement qualifiés par une crate externe et les appels de méthodes .name() ne sont pas assimilés à un item local de même nom.
Un item pub(crate) qui n'est pas clairement consommé hors de son module n'est pas forcé au crate-root par RUST-API-201; sa visibilité peut nécessiter une revue séparée. Dès qu'un réexport crate-root existe, ses références détectées sont toutefois soumises à RUST-IMPORT-201, y compris dans son module de déclaration.
L'audit ne remplace ni cargo check, ni Clippy, ni rustdoc. Il complète ces outils avec une convention architecturale qu'ils ne possèdent pas.