Rust 1.98 est sorti le jeudi vingt août 2026, et la nouveauté mise en avant est une série de méthodes flottantes algébriques sur f32 et f64. Elles autorisent le compilateur à réordonner et vectoriser l'arithmétique en s'appuyant sur les propriétés algébriques des nombres réels, que les flottants ne possèdent pas, dans le même esprit que l'option fast math des autres chaînes de compilation. La différence tient à la portée : Rust place le choix sur des opérations individuelles plutôt que sur une unité de compilation entière. Le changement remonte à un rapport de 2025 signalant un produit scalaire jusqu'à huit fois plus lent que son équivalent C++ sur x86_64 moderne.
The short answer
Rust 1.98 est sorti le jeudi vingt août 2026. Les types f32 et f64 portent désormais des méthodes algébriques pour l'addition, la soustraction, la multiplication, la division et le reste, qui laissent le compilateur réordonner et vectoriser l'arithmétique comme le ferait une option fast math, mais opération par opération plutôt qu'unité de compilation par unité de compilation. Les résultats deviennent non déterministes et ne sont jamais unsound. Les entiers gagnent format_into avec un NumBuffer, et plus de vingt API se stabilisent.
Tout langage qui prend le calcul numérique au sérieux finit par avoir ce débat, et il se conclut en général par une option de compilation qui change discrètement le sens de chaque flottant du programme. Rust a passé des années à refuser ce marché. Avec 1.98, il prend l'optimisation et laisse l'option de côté.
Le problème n'a jamais été la génération de code
Le rapport à l'origine de tout cela se reproduit facilement et se conteste difficilement. Quelqu'un a écrit un produit scalaire en Rust, écrit la même boucle en C++, et mesuré un Rust jusqu'à huit fois plus lent sur x86_64 moderne.
Le code scalaire produit était correct. Le problème est qu'il restait scalaire. L'addition flottante n'est pas associative : quand vous écrivez une somme a + b + c + d, le langage est tenu de l'évaluer comme ((a + b) + c) + d. Une chaîne de dépendances sérielle de ce type ne peut pas être répartie entre les voies vectorielles, donc la boucle ne vectorise jamais. La version C++, généralement compilée avec des réglages flottants permissifs, était libre de regrouper et l'a fait.
Ce n'est pas une faiblesse de génération de code chez Rust. C'est Rust qui honore la sémantique promise, là où une bonne partie de la concurrence ne le fait pas.
Ce que changent les nouvelles méthodes
Les types f32 et f64 exposent désormais des variantes algébriques des cinq opérations arithmétiques. Écrivez la même somme comme une chaîne d'appels algebraic_add et le compilateur peut la regrouper, par exemple en (a + b) + (c + d), pour évaluer les deux sommes partielles en même temps. Une fois la chaîne de dépendances brisée, la vectorisation de la boucle suit généralement d'elle-même.
Les notes de version restent prudentes sur ce qui est promis. L'ensemble exact des optimisations n'est pas spécifié, seulement qu'il peut ressembler à ce que fait une option fast math ailleurs. Vous demandez donc une classe de transformation, pas un contrat sur celles que le backend retient aujourd'hui.
Deux propriétés comptent davantage que la vitesse.
- Les méthodes sont non déterministes. Les mêmes entrées peuvent donner des résultats différents au sein d'une même exécution, parce que le compilateur reste libre de choisir autrement selon les contextes d'inlining.
- Elles ne provoquent jamais de comportement indéfini. Le code unsafe ne doit s'appuyer sur aucune propriété de la valeur de retour pour sa correction, mais rien ici ne peut corrompre la mémoire.
Ce second point est ce qui rend cette livraison raisonnable dans un langage dont l'argument principal est que ses garanties tiennent. Le mode de défaillance, c'est un nombre inattendu, pas un tas mémoire dont on ne peut plus rien tirer.
Pourquoi le point d'appel est le bon endroit
La façon habituelle d'obtenir ces gains est une option de compilation, et son coût habituel est qu'elle s'applique à tout ce qu'elle voit. Activez-la pour accélérer un accumulateur chaud et vous avez aussi relâché les garanties sous un calcul monétaire trois modules plus loin, sous une comparaison dans un utilitaire de test, et sous toute dépendance compilée dans la même unité.
Placer la décision sur l'appel de méthode inverse ce rapport. Vous réécrivez la boucle que vous avez profilée, et le reste du programme conserve la sémantique IEEE 754 ordinaire. Les relecteurs voient le changement dans le diff plutôt que dans un script de compilation. Qui lira ce code dans deux ans saura quelle arithmétique a été volontairement relâchée.
En pratique, cela en fait un outil ciblé et non un interrupteur global. Profilez d'abord, trouvez la réduction qui refuse de vectoriser, réécrivez cette boucle, mesurez à nouveau. Si les termes de la boucle sont d'ordres de grandeur voisins, l'erreur de réordonnancement est en général négligeable. S'il s'agit d'une soustraction de quantités presque égales, ou d'une accumulation sur des valeurs qui couvrent plusieurs ordres de grandeur, n'y touchez pas.
Le formatage entier tamponné
L'autre changement de performance de 1.98 est plus modeste et concernera davantage de bases de code. Les types entiers primitifs gagnent une méthode format_into qui accepte un NumBuffer<Self>.
L'idée est d'éviter la répartition dynamique que traverse la machinerie de formatage standard. Écrire un entier dans un tampon fourni par l'appelant contourne entièrement cette couche, et les notes de version situent le résultat au niveau de bibliothèques spécialisées comme itoa. Qui a déjà ajouté une dépendance uniquement pour sérialiser des entiers rapidement dans un chemin de journalisation ou d'encodage peut désormais la retirer.
Le reste de la version
Plus de vingt API se stabilisent. Les plus susceptibles d'apparaître dans du code ordinaire sont les utilitaires de plage et de sous-chaîne, substr_range, subslice_range et strip_circumfix, les conversions UTF-16 via from_utf16le et from_utf16be et leurs variantes tolérantes, des méthodes d'accès mutable sur les types atomiques, et l'analyse en base pour les entiers NonZero.
La documentation gagne aussi une garantie stable sur le déplacement hors d'un Box abandonné enveloppé dans ManuallyDrop, ce qui formalise un correctif livré dès la version 1.96.0. Rien ne change à l'exécution, mais le comportement sur lequel vous vous appuyiez déjà devient un comportement sur lequel vous avez le droit de vous appuyer.
Quoi faire de cette version
Mettez à jour normalement, rien ici n'est un changement cassant. Ensuite, si vous avez du code numérique, allez regarder les réductions plutôt que les opérations isolées. Les gains des méthodes algébriques se concentrent sur les boucles qui accumulent, puisque ce sont elles dont la dépendance sérielle bloquait la vectorisation.
Nous écririons également, à côté de chaque appel algébrique ajouté, pourquoi le réordonnancement est acceptable pour ce calcul précis. Pas pour le compilateur, qui s'en moque, mais pour la personne qui trouvera plus tard un test qui passe sur une machine et échoue sur une autre, et qui doit savoir en trente secondes si cette boucle en est la cause.
Sources et pour aller plus loin
- Announcing Rust 1.98.0, le blog Rust, 20 août 2026
- Rust 1.98 Adds Algebraic Floating-Point Methods Akin To ffast-math, Phoronix, 20 août 2026
Questions fréquentes
Que sont exactement ces méthodes flottantes algébriques ?
Ce sont des méthodes sur les types primitifs f32 et f64 couvrant l'addition, la soustraction, la multiplication, la division et le reste, nommées algebraic_add et ainsi de suite. Les appeler indique au compilateur qu'il peut optimiser en s'appuyant sur les propriétés algébriques des nombres réels, même si ces propriétés ne valent pas pour les flottants. L'exemple canonique est l'associativité : a + b + c + d doit être évalué de gauche à droite tel qu'écrit, alors qu'une chaîne d'appels algebraic_add peut être regroupée en (a + b) + (c + d) pour calculer les sommes partielles en parallèle. Une vectorisation de boucle plus large suit souvent.
Est-ce la même chose que compiler avec fast math ?
C'est la même famille d'optimisation avec un rayon d'action bien plus étroit. Une option fast math s'applique à toute une unité de compilation, donc chaque opération flottante de la portée perd ses garanties, que vous y ayez pensé ou non. Rust place la décision au point d'appel, ce qui permet d'autoriser le réordonnancement sur une boucle d'accumulation précise en laissant le reste de vos calculs intact. L'ensemble exact des optimisations n'est volontairement pas spécifié : vous obtenez une classe de transformation, pas un contrat sur celles qui s'appliquent.
Ces méthodes peuvent-elles introduire un comportement indéfini ?
Non. La documentation dit explicitement que ces méthodes sont non déterministes, puisque le compilateur reste libre de choisir des optimisations différentes, mais qu'elles ne provoquent jamais de comportement indéfini. Elle avertit aussi que les mêmes entrées peuvent donner des résultats différents au sein d'une même exécution, et que le code unsafe ne doit s'appuyer sur aucune propriété de la valeur de retour pour sa correction. Le risque est donc numérique et non mémoire : les résultats peuvent bouger, et tout ce qui en aval traite une comparaison de flottants comme un invariant mérite un examen.
D où vient le facteur huit ?
D'un rapport de 2025 signalant que de simples produits scalaires en Rust tournaient jusqu'à huit fois plus lentement qu'en C++ sur les processeurs x86_64 modernes. La cause n'était pas la qualité générale du code généré, mais l'impossibilité légale pour le compilateur de réordonner l'accumulation flottante : la boucle ne vectorisait donc jamais, alors que la version C++, généralement compilée avec des réglages flottants permissifs, y parvenait. Les méthodes algébriques existent pour donner à Rust un moyen, dans le langage, d'accorder cette permission.
Que contient Rust 1.98 par ailleurs ?
Le formatage entier tamponné arrive : les types entiers primitifs gagnent une méthode format_into prenant un NumBuffer, ce qui évite la répartition dynamique de la machinerie de formatage habituelle et atteint le niveau d'une bibliothèque spécialisée comme itoa. La documentation garantit désormais de façon stable le déplacement hors d'un Box abandonné enveloppé dans ManuallyDrop, formalisant un correctif de la version 1.96.0. Plus de vingt API se stabilisent, dont substr_range, subslice_range, strip_circumfix, les conversions from_utf16le et from_utf16be avec leurs variantes tolérantes, des méthodes d'accès mutable sur les atomiques et l'analyse en base pour les entiers NonZero.