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 :
default-features = falsesur lesicu_*— évite la featurestdpar défaut (la cause du « mur std » constaté antérieurement, quand les deps du workspace n’avaient pas ce réglage).- build
--release— évite un repli de loggingstd::eprintlnDEBUG-only d’icu_provider(#[cfg(all(not(feature = "logging"), debug_assertions, feature = "alloc", not(target_os = "none")))]), inactif horsdebug_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
- 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). - 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/AirDateTimed’air-base-liben sont l’assise ; ce qui manque est la face C-ABI (libc), pas le moteur. - 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. - Réglages de consommation gravés : sur la cible, les deps
icu_*se consomment endefault-features = falseet se buildent en release. (Le host/gnu peut garderstd.)
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éclarentextern crate alloc;(elles allouent —Vec/String/Boxpour les sorties) ; zéro IO (pas de fichier, réseau, syscall, horloge, thread). L’allocateur global sur la cible =air-alloc(ADR-056). stdmécaniquement exclu. La cible*-linux-airn’a pas destd(build-stdne bâtit quecore+alloc) → tout usagestd= erreur de compilation. La sonde a compilé en release ⇒ zérostdatteint (garantie du compilateur, pas une promesse). Le seulstdnon-test du clos est le repli loggingeprintlnd’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 enconstRust (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 purcore/allocpar-dessus ces tables. L’allocation ne sert qu’aux résultats, jamais aux tables. ⇒ conforme à la politique Air « zéro surface C/C++ » (gatecheck-c-surface). libm: icu atteint la math viacore_maths→ le cratelibm(rust-lang,=0.2.16) — le même que vendoreair-libm(ADR-057), unifié en une seule copie ;libmest du Rust pur (portagefdlibm), pas la libm C du système. icu ne passe pas par la façadeair-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→# = le chantier principal (débloque la fermeture couche 1 complète, i18n comprise, pour*-linux-air).- deps
icu_*d’air-base-lib→default-features = false(1 ligne × 6). air-stringdual-face : ajouter la face C-ABI (libc) au-dessus de l’AirStringexistant.- 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
- Graver (ce document) + rétrécir la roadmap §2.A + consigner la vérification dans l’audit.
- Chantier
air-base-lib#, puis re-sonder la fermeture couche 1 complète pour*-linux-air. - Face C-ABI d’
air-stringquand la surface libc la réclamera.