Les erreurs irrécupérables avec panic!

Parfois, des choses se passent mal dans votre code, et vous ne pouvez rien y faire. Pour ces cas-là, Rust a la macro panic!. Quand la macro panic! s'exécute, votre programme va afficher un message d'erreur, dérouler et nettoyer la pile, et ensuite fermer le programme. Nous allons souvent faire paniquer le programme lorsqu'un bogue a été détecté, et qu"on ne sait comment gérer cette erreur au moment de l'écriture de notre programme.

Dérouler la pile ou abandonner suite à un panic!

Par défaut, quand un panic se produit, le programme se met à dérouler, ce qui veut dire que Rust retourne en arrière dans la pile et nettoie les données de chaque fonction qu'il rencontre sur son passage. Cependant, cette marche arrière et le nettoyage demandent beaucoup de travail. Toutefois, Rust vous permet de choisir l'alternative d'abandonner immédiatement, ce qui arrête le programme sans nettoyage. La mémoire qu'utilisait le programme devra ensuite être nettoyée par le système d'exploitation. Si dans votre projet vous avez besoin de construire un exécutable le plus petit possible, vous pouvez passer du déroulage à l'abandon lors d'un panic en ajoutant panic = 'abort' aux sections [profile] appropriées dans votre fichier Cargo.toml. Par exemple, si vous souhaitez abandonner lors d'un panic en mode publication (release), ajoutez ceci :

[profile.release]
panic = 'abort'

Essayons d'appeler panic! dans un programme simple :

Fichier : src/main.rs

fn main() {
    panic!("crash and burn");
}

Lorsque vous lancez le programme, vous allez voir quelque chose comme ceci :

$ cargo run
   Compiling panic v0.1.0 (file:///projects/panic)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/panic`
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

L'appel à panic! déclenche le message d'erreur présent dans les deux dernières lignes. La première ligne affiche notre message associé au panic et l'emplacement dans notre code source où se produit le panic : src/main.rs:2:5 indique que c'est à la seconde ligne et au cinquième caractère de notre fichier src/main.rs.

Dans cet exemple, la ligne indiquée fait partie de notre code, et si nous allons voir cette ligne, nous verrons l'appel à la macro panic!. Dans d'autres cas, l'appel de panic! pourrait se produire dans du code que notre code utilise. Le nom du fichier et la ligne indiquée par le message d'erreur seront alors ceux du code de quelqu'un d'autre où la macro panic! est appelée, et non pas la ligne de notre code qui nous a mené à cet appel de panic!. Nous pouvons utiliser le retraçage des fonctions qui ont appelé panic! pour repérer la partie de notre code qui pose problème. Nous allons maintenant parler plus en détail du retraçage.

Utiliser le retraçage de panic!

Analysons un autre exemple pour voir ce qui se passe lors d'un appel de panic! qui se produit dans une bibliothèque à cause d'un bogue dans notre code plutôt qu'un appel à la macro directement. L'encart 9-1 montre du code qui essaye d'accéder à un indice d'un vecteur en dehors de l'intervalle des indices valides.

Fichier : src/main.rs

fn main() {
    let v = vec![1, 2, 3];

    v[99];
}

Encart 9-1 : tentative d'accès à un élément qui dépasse de l'intervalle d'un vecteur, ce qui provoque un panic!

Ici, nous essayons d'accéder au centième élément de notre vecteur (qui est à l'indice 99 car l'indexation commence à zéro), mais le vecteur a seulement trois éléments. Dans ce cas, Rust va paniquer. Utiliser [] est censé retourner un élément, mais si vous lui donnez un indice invalide, Rust ne pourra pas retourner un élément acceptable dans ce cas.

En C, tenter de lire au-delà de la fin d'une structure de données suit un comportement indéfini. Vous pourriez récupérer la valeur à l'emplacement mémoire qui correspondrait à l'élément demandé de la structure de données, même si cette partie de la mémoire n'appartient pas à cette structure de données. C'est ce qu'on appelle une lecture hors limites et cela peut mener à des failles de sécurité si un attaquant a la possibilité de contrôler l'indice de telle manière qu'il puisse lire les données qui ne devraient pas être lisibles en dehors de la structure de données.

Afin de protéger votre programme de ce genre de vulnérabilité, si vous essayez de lire un élément à un indice qui n'existe pas, Rust va arrêter l'exécution et refuser de continuer. Essayez et vous verrez :

$ cargo run
   Compiling panic v0.1.0 (file:///projects/panic)
    Finished dev [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Cette erreur mentionne la ligne 4 de notre fichier main.rs où on essaie d'accéder à l'indice 99. La ligne suivante nous informe que nous pouvons régler la variable d'environnement RUST_BACKTRACE pour obtenir le retraçage de ce qui s'est exactement passé pour mener à cette erreur. Un retraçage consiste à lister toutes les fonctions qui ont été appelées pour arriver jusqu'à ce point. En Rust, le retraçage fonctionne comme dans d'autres langages : le secret pour lire le retraçage est de commencer d'en haut et lire jusqu'à ce que vous voyiez les fichiers que vous avez écrits. C'est l'endroit où s'est produit le problème. Les lignes avant cet endroit est du code qui a été appelé par votre propre code ; les lignes qui suivent représentent le code qui a appelé votre code. Ces lignes "avant et après" peuvent être du code du cœur de Rust, du code de la bibliothèque standard, ou des crates que vous utilisez. Essayons d'obtenir un retraçage en réglant la variable d'environnement RUST_BACKTRACE à n'importe quelle valeur autre que 0. L'encart 9-2 nous montre un retour similaire à ce que vous devriez voir :

$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/std/src/panicking.rs:483
   1: core::panicking::panic_fmt
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:85
   2: core::panicking::panic_bounds_check
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/panicking.rs:62
   3: <usize as core::slice::index::SliceIndex<[T]>>::index
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:255
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/slice/index.rs:15
   5: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/alloc/src/vec.rs:1982
   6: panic::main
             at ./src/main.rs:4
   7: core::ops::function::FnOnce::call_once
             at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4/library/core/src/ops/function.rs:227
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Encart 9-2 : le retraçage généré par l'appel de panic! qui s'affiche quand la variable d'environnement RUST_BACKTRACE est définie

Cela fait beaucoup de contenu ! Ce que vous voyez sur votre machine peut être différent en fonction de votre système d'exploitation et de votre version de Rust. Pour avoir le retraçage avec ces informations, les symboles de débogage doivent être activés. Les symboles de débogage sont activés par défaut quand on utilise cargo build ou cargo run sans le drapeau --release, comme c'est le cas ici.

Dans l'encart 9-2, la ligne 6 du retraçage nous montre la ligne de notre projet qui provoque le problème : la ligne 4 de src/main.rs. Si nous ne voulons pas que notre programme panique, le premier endroit que nous devrions inspecter est l'emplacement cité par la première ligne qui mentionne du code que nous avons écrit. Dans l'encart 9-1, où nous avons délibérément écrit du code qui panique, la solution pour ne pas paniquer est de ne pas demander un élément en dehors de l'intervalle des indices du vecteur. À l'avenir, quand votre code paniquera, vous aurez besoin de prendre des dispositions dans votre code pour les valeurs qui font paniquer et de coder quoi faire lorsque cela se produit.

Nous reviendrons sur le cas du panic! et sur les cas où nous devrions et ne devrions pas utiliser panic! pour gérer les conditions d'erreur plus tard à la fin de ce chapitre. Pour le moment, nous allons voir comment gérer une erreur en utilisant Result.