Deux développeurs vus de dos, assis côte à côte devant de grands écrans de code dans un bureau chaleureux.

Rust 1.100 stabilise le type never et l’API Allocator

Rust 1.100.0 est sortie le 12 novembre. Elle stabilise le type never, noté !, au terme de dix ans et de cinq tentatives avortées, ainsi que le cœur de l’API d’allocation mémoire. Côté Cargo, un nouveau réglage permet d’ignorer les versions de dépendances publiées trop récemment, une parade directe aux attaques de chaîne d’approvisionnement.

Le type never, enfin utilisable partout

Selon l’équipe Rust, le type ! est le type « canonique » inhabité : celui des calculs qui ne rendent jamais de valeur et après lesquels le code est inatteignable. return, break ou std::process::exit l’avaient déjà, et il pouvait déjà figurer dans le type de retour d’une fonction (-> !). La nouveauté, c’est qu’on peut désormais le nommer n’importe où. Écrire Result<T, !> pour dire qu’une erreur est impossible, par exemple, était refusé jusqu’ici.

Le billet de blog précise que ce type se distingue des autres types inhabités par sa coercition vers n’importe quel autre type, ce qui permet à une branche de match de quitter la fonction sans produire de valeur.

Le même mouvement fait de Infallible un simple alias de !. Toutes les anciennes API qui employaient Infallible deviennent ainsi compatibles avec le nouveau type. C’était le plan depuis l’introduction d’Infallible, et l’une des raisons pour lesquelles la stabilisation a été si pénible, reconnaît l’équipe.

Le prix à payer est un changement incompatible. Le « repli » du type never (never type fallback), qui valait (), passe à ! dans les éditions 2015, 2018 et 2021, comme c’était déjà le cas en 2024. L’équipe estime que l’effet devrait passer largement inaperçu. Il peut toutefois se manifester dans de rares cas, par exemple lorsqu’on ignore le résultat d’une fonction qui renvoie un paramètre générique, et le compilateur peut repérer davantage de code inatteignable. Des avertissements de compatibilité future ont été émis très en amont et des correctifs publiés pour des crates publiques touchées. Si une dépendance casse, le conseil officiel est de la mettre à jour.

Vue de dessus d'un bureau : clavier mécanique, carnet de croquis, sablier, chronomètre, tasse de café et petit colis ficelé.

Allocateurs personnalisés, DropGuard et conjure_zst

Deuxième gros morceau : la stabilisation des parties centrales d’allocator_api. Les auteurs de bibliothèques peuvent définir leurs propres allocateurs, via le trait Allocator désormais stable, et les utiliser avec Vec et Box, et pour l’instant avec eux seuls. Les notes de version listent en parallèle la stabilisation des allocateurs personnalisés et la généralisation de certaines implémentations de Box à des allocateurs arbitraires, ce qui peut provoquer de nouvelles erreurs d’inférence de types.

Le chantier est loin d’être clos. L’équipe y voit la fondation de son objectif « Allocators 1.0 » et reconnaît qu’il reste beaucoup à faire : élargir la surface d’API utilisable avec ces allocateurs et les stabiliser sur d’autres collections et types de pointeurs. Sur le dépôt rust-lang/rust, le ticket de suivi n° 32838, consacré aux traits d’allocation et à std::heap, apparaît désormais comme fermé. Pour LWN, cette version contient d’ailleurs tant de fonctions stabilisées que bien des utilisateurs qui dépendaient de fonctions instables pourraient basculer sur le compilateur stable.

Autre ajout : core::mem::DropGuard, un type qui exécute une fermeture quand il est détruit, quel que soit le chemin de sortie de la fonction (retour, opérateur ? ou panique). Il se manipule comme la variable qu’il enveloppe, et sa méthode dismiss rend la variable sans lancer la fermeture. L’exemple du billet en tire un analyseur « tout ou rien » : si l’un des textes ne se convertit pas en entier, le vecteur retrouve sa taille d’origine.

Enfin, std::mem::conjure_zst offre une manière dédiée de créer une valeur de type de taille nulle en code unsafe, à la place de la transmutation de () ou de MaybeUninit::uninit().assume_init(). L’équipe rappelle que les règles de l’unsafe sont encore en discussion, et cette fonction sert à fixer ce que signifie précisément un tel geste.

Cargo : attendre avant de faire confiance

C’est sans doute l’ajout qui concerne le plus de monde. Cargo introduit des réglages d’âge minimal de publication, aussi appelés « dependency cooldowns » (RFC 3923). Quand la configuration correspondante est activée, le résolveur ignore les versions publiées plus récemment que le seuil fixé, sauf si elles figurent déjà dans Cargo.lock. LWN présente explicitement la fonction comme un moyen de freiner les attaques sur la chaîne d’approvisionnement.

Le principe est simple : ne pas adopter une version de dépendance dès sa publication, mais attendre qu’elle ait atteint un âge minimal. Les notes de version n’avancent toutefois ni durée recommandée ni valeur par défaut, et rien n’indique que le réglage soit actif sans action de l’utilisateur. Le délai ne s’applique de plus qu’aux nouvelles résolutions : une version déjà inscrite dans Cargo.lock reste utilisée.

D’autres changements touchent à l’outillage :

  • Cargo dispose de son propre système de lints, configurable dans la table [lints.cargo], avec un contrôle des dépendances inutilisées (cargo::unused_dependencies).
  • cargo install utilise par défaut le fichier de verrouillage présent dans l’archive .crate, et --locked devient sans effet.
  • Les artefacts de compilation intermédiaires changent d’arborescence. Les outils qui inspectent ce dossier doivent passer par les messages JSON de Cargo, la disposition n’étant pas une interface stable.

Ce qui peut casser, et ce qu’on ne sait pas

La liste des incompatibilités est longue. Les fermetures capturent plus précisément les variables utilisées dans if let, ce qui peut produire de nouvelles erreurs, la vérification de la compatibilité dyn des objets-traits couvre plus de cas, et les motifs box ne sont plus acceptés par la syntaxe. Iterator::min et max (et leurs variantes _by_key) changent de méthodes Ord : sans effet pour les types qui respectent les règles, mais pas forcément pour les autres. La bibliothèque standard passe à Unicode 18.0.0.

Les cibles évoluent aussi : wasm32-wasip3 passe en Tier 2, i686-pc-windows-gnu devient une cible Tier 2 sans outils hôtes, et powerpc64-sony-ps3 entre en Tier 3. Sur ARM, les valeurs de -Ctarget-feature ou -Ctarget-cpu qui modifieraient l’ABI sont désormais des erreurs, et plusieurs cibles ARMv7-A en virgule flottante matérielle exigeaient par erreur, depuis la 1.94, le FPU VFPv3-D32 : si la correction de cette erreur casse une compilation, il suffit de passer -Ctarget-feature=+d32.

Reste ce que les documents ne disent pas : aucun chiffre sur l’adoption attendue du délai de carence, aucun calendrier précis pour étendre les allocateurs à d’autres collections, l’équipe promettant seulement d’autres stabilisations « bientôt ». À surveiller : les prochaines versions stables, pour voir quelles collections rejoindront Vec et Box.

Sources