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-059 — Stratégie i18n : Air utilise icu4x (pas de réimplémentation), possède l’API + les données

Statut : Accepté (2026-07-01, ratifié BDFL en session sur décision data-backée). RFC de structure (ADR-015). Companion de ADR-053 (la libc Air s’appuie sur icu4x — affirmé), ADR-016 (icu4x = exception i18n), ADR-024 (règle des 80 % / exceptions nommées), ADR-050 (cible *-linux-air). S’appuie sur l’audit docs/notes/audit-icu4x-rust-pur-fr.md et sur la vérification empirique ci-dessous.

Catégorie : Architecture (couche 1, i18n / stratégie). Toute évolution structurante = RFC.

Contexte

La roadmap de clôture de la couche 1 (docs/roadmap-couche1-libc-pal-fr.md §2.A) plaçait l’i18n « Rust pur » comme long pole bi-consommateur (libc et PAL). L’intention initiale était de réimplémenter icu4x en Rust possédé Air (API + structures + tables aux idiomes Rust, en réutilisant les outils icu4x pour le datagen). L’audit audit-icu4x-rust-pur-fr.md avait toutefois établi que le runtime icu4x est déjà no_std et de calcul pur (core + alloc, zéro syscall/horloge/thread), et laissait une question ouverte empirique (§9) : la fermeture icu4x compile-t-elle réellement pour *-linux-air ? — « non fait, à vérifier ».

Vérification empirique (2026-07-01, carbon)

Sonde jetable auto-contenue (hors dépôt) : les 6 composants pinnés (icu_normalizer, icu_casemap, icu_segmenter avec auto/LSTM, icu_collator, icu_locale_core, icu_calendar) en default-features = false + compiled_data, build-std = ["core","alloc"], toolchain nightly pinnée, cible x86_64-unknown-linux-air.json.

Résultat : EXIT=0 (build --release, 33 s). Compilent pour la cible : les 6 composants, tout le socle (zerovec/yoke/zerotrie/potential_utf/writeable/icu_collections/icu_provider/ icu_locale/icu_properties), calendrical_calculations, core_maths et libm. Deux conditions triviales :

  1. default-features = false sur les icu_* — évite la feature std par défaut (la cause du « mur std » constaté antérieurement, quand les deps du workspace n’avaient pas ce réglage).
  2. build --release — évite un repli de logging std::eprintln DEBUG-only d’icu_provider (#[cfg(all(not(feature = "logging"), debug_assertions, feature = "alloc", not(target_os = "none")))]), inactif hors debug_assertions. Air compile la cible en release (panic=abort, ADR-025).

Réimplémenter le socle icu n’est PAS une nécessité technique. Ce serait un effort de plusieurs mois pour reproduire à la main ~15 crates de logique Unicode ULP-correcte (zéro-copie, tries, collation UCA, LSTM, astronomie calendaire) qui compilent déjà no_std pour la cible.

Décision — Option V

  1. Air UTILISE icu4x (extension de l’exception nommée ADR-016 ; docs/EXCEPTIONS.md). Aucune réimplémentation en bloc du socle icu. La réécriture éventuelle reste en réserve, par composant, et seulement si un besoin doctrinal précis l’exige (jamais par principe).
  2. Air POSSÈDE l’API Air-facing : air-string (dual-face — la face que voient la libc C-ABI et le PAL Rust). L’AirString/AirLocale/AirDateTime d’air-base-lib en sont l’assise ; ce qui manque est la face C-ABI (libc), pas le moteur.
  3. Air POSSÈDE (à terme) le pipeline de données : compiled_data (tables baked amont) suffit pour démarrer ; un datagen Air (host, réutilisant le datagen icu4x sur sources CLDR/UCD) est optionnel et différé — à décider quand Air voudra maîtriser ses propres tables.
  4. Réglages de consommation gravés : sur la cible, les deps icu_* se consomment en default-features = false et se buildent en release. (Le host/gnu peut garder std.)

Nature de ce qu’Air embarque (Rust pur, pas ICU4C)

À ne pas confondre : ICU4C est la bibliothèque i18n historique en C/C++ ; icu4x est une réimplémentation from scratch en Rust pur no_std (unicode-org) — aucun binding vers ICU4C, aucun FFI, aucun C. Ce qu’Air compile dans son binaire cible est donc du code machine issu de Rust uniquement. Vérifié :

  • Repose sur core + alloc, rien d’autre. Toutes les crates icu déclarent extern crate alloc; (elles allouentVec/String/Box pour les sorties) ; zéro IO (pas de fichier, réseau, syscall, horloge, thread). L’allocateur global sur la cible = air-alloc (ADR-056).
  • std mécaniquement exclu. La cible *-linux-air n’a pas de std (build-std ne bâtit que core+alloc) → tout usage std = erreur de compilation. La sonde a compilé en release ⇒ zéro std atteint (garantie du compilateur, pas une promesse). Le seul std non-test du clos est le repli logging eprintln d’icu_provider, debug_assertions-only (inactif en release) ; tout le reste (std::collections/sync::LazyLock/panic/…) est #[cfg(test)] (host, hors cible).
  • Forme du runtime = « tables const + fonctions pures ». Les données Unicode/CLDR sont générées à la build (outil host, datagen) et embarquées en const Rust (compiled_data, en .rodata) ; les crates zéro-copie (zerovec/yoke/potential_utf) voient ces tables sans les copier ni les allouer. Le calcul (lookup de trie, collation UCA, arithmétique calendaire, matrices LSTM → libm) est du pur core/alloc par-dessus ces tables. L’allocation ne sert qu’aux résultats, jamais aux tables. ⇒ conforme à la politique Air « zéro surface C/C++ » (gate check-c-surface).
  • libm : icu atteint la math via core_maths → le crate libm (rust-lang, =0.2.16) — le même que vendore air-libm (ADR-057), unifié en une seule copie ; libm est du Rust pur (portage fdlibm), pas la libm C du système. icu ne passe pas par la façade air-libm (qui reste la surface math Air publique, non requise par icu).

Conséquences — la roadmap §2.A est rétrécie

Le « long pole » cesse d’en être un. Le travail restant devient du no_std / nettoyage :

  • air-base-lib#![no_std] (ex-2.A.5) = le chantier principal (débloque la fermeture couche 1 complète, i18n comprise, pour *-linux-air).
  • deps icu_* d’air-base-libdefault-features = false (1 ligne × 6).
  • air-string dual-face : ajouter la face C-ABI (libc) au-dessus de l’AirString existant.
  • pipeline datagen Air : optionnel / plus tard.

Abandonnées (inutiles puisque icu4x compile tel quel) : ex-2.A.1 (vendoring des 6 utils), ex-2.A.2 (port du socle), ex-2.A.3 (port de calendrical_calculations). Note libm : air-libm (ADR-057) n’est pas requis par icu (icu tire le crate libm via core_maths directement) ; air-libm reste utile comme surface math Air publique, mais la dépendance i18n est satisfaite en amont.

Alternatives rejetées

  • Option R — réimplémenter le socle en bloc : coût (mois) et risque de correction (ULP) disproportionnés pour reproduire du code amont maintenu et déjà no_std-compatible cible. Contraire au Principe 5 (ne pas sur-produire) et à l’esprit de l’exception ADR-016.
  • Option Hybride — posséder d’emblée certains composants (ctype/properties) : prématuré ; on peut basculer un composant vers du possédé plus tard, au cas par cas, sans le décider maintenant.

Suite

  1. Graver (ce document) + rétrécir la roadmap §2.A + consigner la vérification dans l’audit.
  2. Chantier air-base-lib #![no_std] (+ default-features = false sur les icu_*), puis re-sonder la fermeture couche 1 complète pour *-linux-air.
  3. Face C-ABI d’air-string quand la surface libc la réclamera.