DevNews

Rust 1.98 ajoute un calcul flottant algébrique opt-in

Sur cette page
  1. Le problème n'a jamais été la génération de code
  2. Ce que changent les nouvelles méthodes
  3. Pourquoi le point d'appel est le bon endroit
  4. Le formatage entier tamponné
  5. Le reste de la version
  6. Quoi faire de cette version
  7. Sources et pour aller plus loin

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.

20 aoûtsortie de Rust 1.98, 2026
8xécart sur le produit scalaire à l origine
20+API nouvellement stabilisées
Carte réponse : Rust 1.98 est sorti le 20 août 2026 avec des méthodes algébriques d'addition, de soustraction, de multiplication, de division et de reste sur f32 et f64 qui autorisent un réordonnancement de type fast math opération par opération, ainsi qu'une méthode format_into utilisant NumBuffer pour formater les entiers sans allocation.
Le fast math, limité à l'opération plutôt qu'à la compilation. PNG

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.

Comparaison de portée entre une option fast math appliquée à toute l'unité de compilation, qui modifie chaque opération flottante de la construction y compris le code que vous n'avez pas écrit, et les méthodes algébriques de Rust 1.98, qui ne modifient que les points d'appel réécrits et laissent le reste du programme sous sémantique IEEE normale.
Même optimisation, rayon d'action très différent. PNG

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

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.