Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-090 — Binding de la couche 1 dans std par rustc-dep-of-std : découplage plutôt que fork, sans descellement de la couche 0

Statut : Accepté (2026-07-11, décision BDFL). Met en œuvre ADR-088 (le PAL std custom safe bindant la couche 1). S’appuie sur ADR-054 (scission air-base-core / i18n). Encadre l’usage des exceptions 80 % ADR-016/ADR-059 (icu4x) et ADR-034 (RustCrypto) dans la fermeture de std. Relève de la doctrine dépendances ADR-024 (règle 80 %).

Catégorie : Décision de toolchain (comment le toit std consomme la couche 1). N’altère aucune API couche 0 scellée ni couche 1 en sémantique ; ce sont des additifs build-config + une discipline de layering.

Instruit par la mesure (host-first, exploratoire, branche feat/pal-safe-air-target, incréments 0→3 — voir rt/DECISIONS.md et etude-std-pal-air-safe-fr.md).

Contexte

ADR-088 pose que std repose sur un PAL safe bindant les Managers couche 1. Restait la question de mécanique : comment un Manager couche 1 (Rust safe) entre-t-il dans le graphe de dépendances de std ? La reconnaissance (incréments 0→3) a mesuré la réponse :

  1. Le motif est rustc-dep-of-std (précédent libc/hashbrown/hermit-abi) : une crate qui entre dans la fermeture de std est bâtie no_std avec une feature rustc-dep-of-std (passthrough vers core/compiler_builtins), car elle ne peut pas dépendre de std (elle est ce à partir de quoi std est bâtie — sinon cycle).
  2. Coût côté Air = quasi nul. Binder air-alloc réel (incrément 2) n’a touché que 3 Cargo.toml (feature rustc-dep-of-std sur air-alloc/air-sys-syscall/air-sys-types, +38 lignes) — zéro source .rs, zéro changement sémantique, zéro gate de couverture. Les crates Air visent déjà no_std (Principe 4). Le descellement formel de la couche 0 n’est donc PAS nécessaire.
  3. Le vrai mur est externe. Toute dépendance externe tirée dans la fermeture de std qui n’a pas rustc-dep-of-std amont heurte l’erreur « two cores » (E0463) et impose un fork vendored (le cas bitflags, incrément 2). L’audit (incrément 3) a chiffré le binding naïf de tous les Managers à ~71 forks (33 icu4x via air-base-lib + 37 RustCrypto via air-crypto + bitflags) — intenable.
  4. Mais ce fardeau est accidentel, donc évitable. Les deux grappes lourdes n’entrent dans std que par couplage de chemin d’import : (a) AirError/AirResult/AirPath sont définis dans air-base-core (icu-free) ; air-base-lib ne fait que les ré-exporter en y ajoutant icu4x — un Manager qui importe depuis air-base-lib traîne inutilement 33 crates ; (b) AirRandom::fill n’appelle que getrandom (couche 0) — le rattacher à un chemin crypto traîne inutilement 37 crates. Ni icu4x ni RustCrypto ne sont utilisés par std (agnostique à la locale ; pas de crypto dans le PAL).

Décision

Les Managers couche 1 entrent dans std par rustc-dep-of-std (build-config), et la fermeture de std est gardée minimale par découplage, jamais par fork des exceptions 80 %.

  1. Mécanisme : rustc-dep-of-std, pas de descellement. Une crate Air dans la fermeture de std reçoit une feature rustc-dep-of-std (build-config) et reste no_std. Aucun changement sémantique d’une API couche 0 scellée ou couche 1. Pas de re-tag de la couche 0.

  2. Discipline de découplage (absolue) — air-base-core, jamais air-base-lib. Tout bras PAL sys/<domaine>/air.rs, et tout Manager destiné à la fermeture de std, importe ses types transverses (erreurs, chemins, temps monotone, os_str, log, encodages) depuis air-base-core (icu-free), JAMAIS air-base-lib (qui tire icu4x). air-base-lib et AirString+icu4x restent le domaine applicatif (couche 2 / apps, consommateurs de std, au-dessus).

  3. Interdiction des exceptions 80 % lourdes dans la fermeture de std. Binder un Manager dans std interdit de tirer icu4x (ADR-016/059) ou RustCrypto (ADR-034) — ou toute exception 80 % à grande grappe — dans sa fermeture. Ces exceptions vivent au niveau consommateur (couche 2 libair, applications), au-dessus de std, où std::fs + String + AirString

    • icu4x cohabitent librement. Elles ne descendent jamais dans la fondation.
  4. AirRandom / random sur un chemin sans crate crypto. Le bras sys/random/air est rattaché à un chemin qui n’entraîne pas RustCrypto (isolation — feature minimale sur air-crypto, ou micro-surface dédiée). AirRandom::fill ne fait qu’appeler getrandom.

  5. Frontière no_std = relation à std, pas un numéro de couche. Composant à partir duquel std est bâtie (couche 0, couche 1, backend PAL) ⇒ no_std pour toujours (dépendance de std, ne peut en dépendre). Consommateur de std (couche 2 libair, applications) ⇒ std complet. String/Vec (types d’alloc, = std::String) sont disponibles dès la couche 1 ; AirString (i18n) est un type applicatif, jamais requis par le PAL.

  6. Politique de fork des dépendances externes (règle 80 %). Une dépendance externe sans rustc-dep-of-std amont, tirée dans la fermeture de std, impose un fork vendored localisé (patch + script, façon bitflags, réversible). On minimise ce nombre : auditer avant de binder, préférer le découplage (déplacer l’import) ou le retrait / sous-module (règle 80 %, ADR-024) au fork. On ne forke PAS icu4x ni RustCrypto (exceptions ratifiées, réimplémentation exclue) — on les garde hors de la fermeture de std.

  7. Industrialisation par vagues. (a) Vague 0 : binding réel du noyau gratuit (air-base-core, air-thread/futex+sync, air-handle, air-stdio, air-env, + air-alloc), fermeture propre = 1 seul fork (bitflags). (b) Vague 1 : air-filesystem/air-socket/ air-process/air-runtime après redirection d’imports → air-base-core + additif current_exe. (c) Vague 2 : random isolé + correction du bug spawn natif aarch64 (thread 2 arches). Jalon : hello-std tourne sur le PAL safe, 2 arches, sans libair_c liée (objectif ADR-088).

Conséquences

  • La couche 0 scellée reste intacte (build-config seul) ; la couche 1 évolue par re-sceau additif (current_exe, éventuel ReadDir paresseux) — pas de refonte.
  • Chaque programme Air garde une fondation minimale : std ne traîne ni icu4x ni RustCrypto (ni leurs tables de données baked-in) — cohérent avec les budgets mémoire (Pi 4) et la fondation safe/auditable d’ADR-088. En static-PIE, un hello world ne paie pas l’Unicode.
  • Dette de fork bornée à 1 (bitflags) tant que la discipline de découplage tient ; toute nouvelle dép externe dans la fermeture de std passe par l’audit (règle 80 %).
  • air-base-lib conserve son rôle d’API i18n historique, au niveau consommateur — non déprécié, simplement hors du chemin std.
  • Caveat outillage : le motif rustc-dep-of-std laisse des façades rustc-std-workspace-* dans Cargo.lock (jamais compilées feature OFF) — vérifier cargo deny/audit/machete et la repro bit-pour-bit (ADR-025) avant tout merge sur main.
  • Lien RFC amont : ce mécanisme downstream (fork rust-src + rustc-dep-of-std) est ce que l’étage 2 de l’étude RFC (etude-rfc-trait-backend-std-fr.md, backend hors-arbre via -Z build-std-platform) viserait à remplacer côté amont. La preuve downstream nourrit l’RFC.

Alternatives rejetées

  • Poser std sur air-base-lib (avec icu4x) : rejeté — fait entrer tout icu4x (~33 crates
    • tables de données) dans la fondation de chaque programme, pour une capacité que std n’utilise jamais (agnostique à la locale) ; risque de cycle (default d’icu4x tire std — ADR-054) ; ~33 forks rustc-dep-of-std à maintenir. Le découplage vers air-base-core l’évite à coût nul.
  • Forker les 33 icu4x / 37 RustCrypto pour rustc-dep-of-std : rejeté — ~71 forks + dette de re-sync, dont 37 crypto sécurité-sensibles (re-audit à chaque sync). Intenable et inutile (deux grappes non utilisées par std).
  • Descelller/refondre la couche 0 pour la rendre dépendance de std : rejeté comme inutile — la mesure (incrément 2) montre que seule une feature build-config est requise ; les crates sont déjà no_std. Un descellement sémantique serait un risque gratuit.
  • Réimplémenter un Unicode/crypto maison pour éviter icu4x/RustCrypto : rejeté — exceptions 80 % ratifiées (ADR-016/059/034) ; hors sujet, car le problème n’est pas la dépendance mais son placement (consommateur, pas fondation).
  • Attendre l’RFC amont (backend std::sys de première classe) avant d’avancer : rejeté comme bloquant — on prouve downstream d’abord, on propose l’RFC ensuite (stratégie ADR-088).