ADR-053 — La libc Air s’appuie sur icu4x pour ctype/locale/i18n (caractères, casse, collation, normalisation, segmentation)
Statut : Accepté (2026-06-30, ratifié par le BDFL). Companion d’ADR-046
(libc Air), ADR-047 (construction libc) et
ADR-016 (i18n / icu4x). Corrige/precise ADR-046 D7 (« libm exclu v1 »,
« hors wide-char lourd » — devenus obsolètes, cf. D6). S’appuie sur l’audit
docs/notes/audit-icu4x-rust-pur-fr.md.
Catégorie : Architecture (libc / i18n / fondateur). Évolution structurante = RFC (ADR-015).
Contexte
Pendant des décennies, les développeurs C ont subi les interprétations par implémenteur de
libc du traitement des caractères et des locales : isprint/isalpha dépendant d’une locale
globale floue, casse cassée hors ASCII, strcoll de qualité variable, locale C naïve,
« implementation-defined » partout. Pour avoir des programmes portables et corrects en i18n,
le développeur sérieux embarque icu4x par-dessus sa libc. Même constat côté Rust : std
(str/String) ne repose pas sur icu4x — il embarque ses propres mini-tables Unicode
(propriétés, casse locale-indépendante) et ne fait ni collation ni normalisation ni
segmentation locale-aware ; pour ça, on importe des crates externes (icu4x). Les deux
écosystèmes laissent donc le i18n correct en bolt-on.
Air refuse cet héritage. La doctrine libc (ADR-046 D3/D5 : « POSIX = objectif fonctionnel, pas
conformité ; SHOULD→MUST ; kernel = bible, pas POSIX ») dit déjà : là où POSIX est faible, on
ne reproduit pas la faiblesse, on donne le comportement défini et correct. L’i18n est le terrain
emblématique de cette doctrine.
Décision
D1 — Le ctype/locale/i18n de la libc Air repose sur icu4x (couche 1)
La surface C de traitement de caractères, locales et i18n s’appuie sur icu4x (déjà adopté en couche 1, ADR-016) :
- classification (
isalpha/isdigit/isprint/iswalpha…) →icu_properties; - casse (
towupper/towlower/wcs*/towctrans) →icu_casemap; - collation (
strcoll/strxfrm/wcscoll) →icu_collator(UCA) ; - normalisation (NFC/NFD/NFKC/NFKD, extensions) →
icu_normalizer; - segmentation (graphème/mot/ligne/phrase, extensions) →
icu_segmenter; - locales (BCP-47, ADR-016 ; pas les noms POSIX) et calendriers →
icu_locale/icu_calendar.
Le développeur C n’a plus à importer icu4x : Air le lui offre au socle. Et là où POSIX a des trous (segmentation, normalisation, casse pleine, collation correcte), Air expose le standard Unicode plutôt que d’imposer la faiblesse POSIX.
D2 — Mapping POSIX ↔ icu4x : granularité (le vrai travail)
L’API POSIX ctype est mono-caractère + locale globale ; icu4x est niveau chaîne + locale explicite + sans état. Réconciliation :
- classification mono-code-point (
isX/iswX) →icu_properties(mapping direct) ; - casse simple mono-caractère (
towupper(c)rend un code-point) → mapping simple 1→1 ; la casse pleine/locale (1→N, règles turques/grecques) sort par les fonctions niveau chaîne (wcs*, ou extensions Air) ; - collation/normalisation/segmentation = intrinsèquement niveau chaîne → fonctions
strcoll/strxfrm/wcscoll+ extensions Air (normaliser, segmenter) exposées au C.
D3 — Locale globale POSIX réconciliée avec le « zéro globale » d’Air
POSIX exige setlocale/une locale globale ; Air veut zéro globale + locale explicite
(ADR-046 D4). On fournit donc une locale courante thread-local (même modèle que errno-in-TCB,
ADR-047/ADR-049) qui alimente les appels icu4x explicites. Interne en BCP-47 ; les noms de
locale POSIX (fr_FR.UTF-8…) sont mappés en BCP-47.
D4 — Garder le byte-level rapide ; icu4x là où il ajoute de la correction
Les chemins triviaux et chauds restent simples et rapides (fast-path ASCII d’isdigit,
strcmp=memcmp, strlen…). icu4x est sollicité pour les opérations locale-aware/Unicode
(collation, casse pleine, normalisation, segmentation, propriétés non-ASCII), pas pour le
byte-level. On n’alourdit pas le commun.
D5 — Écarts à la lettre POSIX documentés (transparence = valeur d’Air)
Là où Air fait Unicode-correct au lieu du quirk C-locale, l’écart est documenté par fonction (doc polyglotte, ADR-027). Le développeur sait exactement ce que fait la fonction — c’est la promesse « une libc qui fait ce qu’elle dit ».
D6 — Conséquences sur le périmètre ADR-046 (ce qui devient obsolète)
libmn’est PLUS « exclu v1 » : icu4x exige unlibmno_std (math decalendrical_calculationspour les calendriers — incontournable — et du LSTM deicu_segmenter). ⇒ ADR-046 D7 « libm exclu de la v1 » est OBSOLÈTE :libmno_std est un prérequis de la i18n Air. (Décision « libm réécrit vs vendoré » = question ouverte.)- Le wide-char/wcs n’est PLUS « hors périmètre » :
wchar/wcs*/towctransest précisément la surface chaîne où la puissance icu4x s’expose. ⇒ ADR-046 D7 « hors wide-char lourd » est OBSOLÈTE dans sa forme absolue : le wcs i18n-aware fait partie du cœur (adossé à icu4x).
D7 — Un seul socle icu4x, trois consommateurs (dual-face étendu)
La même i18n couche 1 (icu4x Air) sert : la libc C (cet ADR), le futur std de
*-linux-air (ses méthodes char/str, qui sinon dupliqueraient des mini-tables), et le
pal. Un i18n maîtrisé, trois surfaces (prolonge le motif dual-face, ADR-046/047).
Conséquences
- L’i18n correcte est par défaut dans la libc Air ; le développeur C n’embarque plus icu4x ; les faiblesses POSIX ne sont plus imposées.
- Prérequis débloqués :
libmno_std (D6), le port icu4x couche 1 (audit : socle no_std calcul-pur + petites crates utilitaires vendorables + pipeline de données CLDR/UCD). - Taille : une libc qui bake des tables CLDR est plus grosse que la locale minimale musl ; mitigeable (tables feature-gated / chargeables) — décision d’implémentation.
- Compat : du code C dépendant du comportement exact C-locale peut différer ; risque faible pour les cibles v1 (OpenSSH, ASCII-centré), couvert par D5 (documentation).
Alternatives écartées
- Locale POSIX minimale (façon musl/glibc) : c’est la faiblesse historique qu’on rejette.
- Laisser le bolt-on icu4x au développeur (statu quo C et Rust std) : Air fait mieux — i18n correcte au socle.
- icu4x seulement aux couches hautes (UI/formats) : raterait la surface C ; or c’est là que des décennies de programmes ont souffert.
Questions ouvertes
libm: réécrire unlibmAir no_std, ou vendorerlibm(exception 80 % nommée) ? (Bloque calendar + LSTM — premier maillon.)- LSTM : segmentation LSTM (floats + modèle) ou dictionnaire seul en v1 ?
- Datagen : réutiliser celui d’icu4x (pragmatique) ou réimplémenter (host, std) ? Sources CLDR/UCD amont dans tous les cas.
- Calendrier de réalisation : la i18n Air vient après le runtime/libc de base, ou en parallèle ?
Licence du document : MPL 2.0.