Débordement d'entiers en Rust : choisir le bon contrat arithmétique

Débordement d’entiers en Rust : choisir le bon contrat arithmétique

Table des matières

J’ai écrit un article sur les différents comportements possibles en cas de dépassement de capacité d’un entier (ou débordement), qui explique comment gérer ce cas et comment les langages de programmation le traitent par défaut.

Cet article reprend le même sujet sous l’angle de Rust : on peut choisir explicitement le comportement à adopter en cas de débordement, et Rust rend ce choix visible dans les noms de ses méthodes.

Par défaut, Rust vérifie les débordements d’entiers dans les compilations en mode débogage et effectue un retour modulo en cas de débordement dans les compilations en mode publication. Cette configuration peut être modifiée ; le profil de compilation ne suffit donc pas à déterminer de façon fiable le comportement de +.

Je n’aime pas particulièrement ce comportement, car il peut entraîner des bugs difficiles à repérer en production si l’on n’y prend pas garde. Je préfère indiquer explicitement le comportement face au débordement qui convient à mon cas d’usage.

Rust propose un large éventail de méthodes arithmétiques qui permettent de choisir comment une opération traite les débordements. Cet article compare la valeur renvoyée par chaque méthode et les garanties qu’elle offre en cas de débordement.

​
Les contrats de débordement des entiers

Nous prendrons l’addition (+) comme exemple pour comparer les principaux comportements possibles en cas de débordement. Rust propose des méthodes analogues pour d’autres opérations, mais leur disponibilité dépend de l’opération et du type entier utilisé.

​
Retour modulo (arithmétique modulaire)

Le modèle du retour modulo maintient la valeur dans les limites du type entier grâce à l’arithmétique modulaire.

Rust propose deux méthodes apparentées :

1fn wrapping_add(self, rhs: Self) -> Self
2fn overflowing_add(self, rhs: Self) -> (Self, bool)

La différence est importante :

  • .wrapping_add() renvoie uniquement le résultat obtenu par retour modulo.
  • .overflowing_add() renvoie le même résultat, accompagné d’un booléen qui indique si un débordement s’est produit.

Exemple :

1let value: u8 = 255;
2
3let wrapped = value.wrapping_add(1); // 0
4let (wrapped2, overflowed) = value.overflowing_add(1); // (0, true)

Le résultat numérique est identique dans les deux cas. La différence tient à l’API : .wrapping_add() ne signale pas le débordement, tandis que .overflowing_add() l’indique au moyen d’un booléen.

Utilisez .wrapping_add() si seul le résultat modulo vous intéresse. Utilisez .overflowing_add() si vous avez aussi besoin de savoir si un débordement s’est produit.

Pour les types entiers natifs, + produit un résultat par retour modulo lorsque les vérifications de débordement sont désactivées, comme .wrapping_add(). Mais contrairement à +, .wrapping_add() garantit ce comportement quelle que soit la configuration de compilation.

​
Arithmétique saturante

La saturation plafonne la valeur à la limite du type au lieu de la faire repartir à l’autre extrémité.

1fn saturating_add(self, rhs: Self) -> Self

Exemple :

1let value: u8 = 255;
2let clamped = value.saturating_add(1); // 255

L’arithmétique saturante est utile lorsqu’une valeur doit rester à sa limite plutôt que de repartir à l’autre extrémité, par exemple pour un contrôle d’interface dont la plage est fixe.

​
Arithmétique avec vérification

La vérification revient à se demander : « Le résultat de cette opération peut-il être représenté ? »

1fn checked_add(self, rhs: Self) -> Option<Self>

Exemple :

1let value: u8 = 255;
2let result = value.checked_add(1); // None

Cette approche répond à un besoin proche de celui de la gestion des exceptions récupérables : l’appelant peut suivre un chemin distinct en cas de débordement. Rust représente explicitement ce résultat par None. Par exemple, un match permet d’utiliser la somme avec Some(sum) et, avec None, de rejeter l’entrée ou de suivre une autre voie de récupération.

Vous pourriez vous demander : « Pourquoi ne pas simplement utiliser .overflowing_add() et vérifier le booléen ? » .checked_add() exprime plus directement l’intention : l’appelant reçoit None lorsque le résultat ne peut pas être représenté.

​
Panique et arrêt immédiat

Avec la configuration par défaut, un débordement de + provoque une panique dans les compilations en mode débogage. Plus généralement, le choix d’une panique en cas de débordement est utile lorsque celui-ci enfreint un invariant : si le résultat ne peut pas être représenté, l’exécution s’arrête au lieu de continuer avec une valeur obtenue par retour modulo.

Les versions nightly ou expérimentales peuvent proposer une API arithmétique plus stricte, comme strict_add(), qui arrête immédiatement l’exécution en cas de débordement. En Rust stable, on peut traiter explicitement le résultat de .checked_add() avec expect(...) pour provoquer une panique en cas d’échec.

Voici l’équivalent d’un contrat avec panique en Rust stable :

1let result = value.checked_add(other).expect("overflow");

L’intention est la même que dans le modèle avec panique : le code refuse de continuer si le résultat de l’opération ne peut pas être représenté. Cette approche a toutefois l’avantage d’être explicite, lisible et facile à maîtriser.

​
Arithmétique non sûre et comportement indéfini

Cette opération n’est pas sûre en Rust, mais la méthode unsafe .unchecked_add() est disponible.

1unsafe fn unchecked_add(self, rhs: Self) -> Self

Exemple :

1let result = unsafe { 10_u8.unchecked_add(1) }; // 11 ; la précondition est respectée
2let value: u8 = 255;
3let result = unsafe { value.unchecked_add(1) }; // Comportement indéfini ; la précondition n'est pas respectée

N’utilisez .unchecked_add() que si vous pouvez prouver que la somme tient toujours dans le type et que des benchmarks montrent un gain dans du code où les performances sont critiques. Sinon, utilisez .checked_add() si vous devez traiter un éventuel débordement, ou + si vous savez que la somme tient dans le type. Le non-respect de la précondition entraîne un comportement indéfini.

À cet égard, .unchecked_add() ressemble à l’addition + sur des entiers signés en C : le compilateur peut supposer qu’aucun débordement ne se produit, et enfreindre cette hypothèse entraîne un comportement indéfini.

​
Le modèle général de nommage des opérations

Rust nomme souvent ses méthodes arithmétiques en combinant un comportement et une opération : wrapping_add, checked_mul (multiplication avec vérification) et overflowing_shl (décalage à gauche avec signalement du débordement). Cependant, chaque comportement n’est pas disponible pour toutes les opérations ni pour tous les types entiers.

Par exemple, les types entiers signés proposent saturating_neg et unchecked_neg, contrairement aux types non signés. Les types non signés proposent checked_neg, qui ne réussit que pour zéro. Les décalages disposent de variantes avec vérification, avec retour modulo, avec signalement du débordement et non sûres, mais pas de variantes saturantes. Ces méthodes portent sur la valeur du décalage : par exemple, checked_shl renvoie None si cette valeur est supérieure ou égale à la largeur de l’entier, et non si des bits non nuls sont décalés hors de l’entier.

Consultez la documentation du type et de l’opération concernés : ne partez pas du principe que chaque comportement est disponible partout.

​
Choisir un comportement en cas de débordement

  • Utilisez l’arithmétique avec retour modulo lorsque vous avez besoin d’un résultat modulaire.
  • Utilisez l’arithmétique avec signalement du débordement si vous avez besoin à la fois du résultat modulo et d’un indicateur de débordement.
  • Utilisez l’arithmétique avec vérification lorsque le débordement doit être représenté par None et traité par l’appelant.
  • Utilisez l’arithmétique saturante lorsque le résultat doit rester à la limite numérique du type.
  • Si un débordement enfreint un invariant, transformez l’échec de l’opération vérifiée en panique, par exemple avec expect(...). N’utilisez l’arithmétique non sûre que si vous pouvez garantir le respect de sa précondition de sûreté.

Tip

Rust permet aussi de traiter explicitement les débordements des nombres à virgule flottante.

Il fournit les méthodes .is_nan() et .is_finite() pour vérifier ces conditions.

Vous apprenez Rust ? Retrouvez ici une sélection de ressources et de conventions de code pour vous aider.

Partager :

Articles Connexes

Comment fonctionnent réellement les barres de progression CLI (c’est juste une ligne réécrite)

Comment fonctionnent réellement les barres de progression CLI (c’est juste une ligne réécrite)

Les barres de progression CLI semblent se mettre à jour sur place, mais ce n’est pas le cas. Le terminal se contente de réécrire la même ligne encore …

Lire la suite about Comment fonctionnent réellement les barres de progression CLI (c’est juste une ligne réécrite)
Comment Faire Parler Votre Terminal en Couleur (avec les Codes ANSI)

Comment Faire Parler Votre Terminal en Couleur (avec les Codes ANSI)

[Dernière mise à jour: 23 mai 2026]

Transformez un terminal gris et monotone en texte coloré et lisible qui parle vraiment votre langage. Vous êtes déjà resté plissé devant un mur de …

Lire la suite about Comment Faire Parler Votre Terminal en Couleur (avec les Codes ANSI)
Robustesse logicielle et paradigmes de timeout/retry avec backoff

Robustesse logicielle et paradigmes de timeout/retry avec backoff

Les programmes accèdent à des ressources externes, notamment des périphériques I/O et des services distants. Ces ressources peuvent être peu fiables, …

Lire la suite about Robustesse logicielle et paradigmes de timeout/retry avec backoff