Les erreurs irrécupérables avec panic!
Parfois, de mauvaises choses se produisent dans votre code, et vous ne pouvez rien y faire. Dans ces cas-là, Rust dispose de la macro panic!. Il y a deux manières de provoquer un panic en pratique : en effectuant une action qui fait paniquer notre code (comme accéder à un tableau au-delà de sa fin) ou en appelant explicitement la macro panic!. Dans les deux cas, nous provoquons un panic dans notre programme. Par défaut, ces panics affichent un message d’erreur, déroulent la pile, nettoient la mémoire et quittent le programme. Via une variable d’environnement, vous pouvez également demander à Rust d’afficher la pile d’appels lorsqu’un panic se produit, afin de faciliter la recherche de son origine.
Dérouler la pile ou abandonner en réponse à un panic
Par défaut, lorsqu’un panic se produit, le programme commence à dérouler la pile (unwinding), ce qui signifie que Rust remonte la pile et nettoie les données de chaque fonction qu’il rencontre. Cependant, remonter et nettoyer demande beaucoup de travail. Rust vous permet donc de choisir l’alternative d’abandonner (aborting) immédiatement, ce qui met fin au programme sans nettoyage.
La mémoire utilisée par le programme devra alors être nettoyée par le système d’exploitation. Si dans votre projet vous avez besoin de rendre le binaire résultant aussi petit que possible, vous pouvez passer du déroulement de pile à l’abandon en cas de panic en ajoutant panic = 'abort' aux sections [profile] appropriées dans votre fichier Cargo.toml. Par exemple, si vous voulez abandonner en cas de panic en mode release, ajoutez ceci{N}:
[profile.release]
panic = 'abort'
Essayons d’appeler panic! dans un programme simple :
fn main() {
panic!("crash and burn");
}
Lorsque vous exécutez le programme, vous verrez quelque chose comme ceci : console {{#include ../listings/ch09-error-handling/no-listing-01-panic/output.txt}}
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
Running `target/debug/panic`
thread 'main' panicked at src/main.rs:2:5:
crash and burn
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
L’appel à panic! provoque le message d’erreur contenu dans les deux dernières lignes. La première ligne affiche notre message de panic et l’endroit dans notre code source où le panic s’est produit : src/main.rs:2:5 indique qu’il s’agit de la deuxième ligne, cinquième caractère de notre fichier src/main.rs.
Dans ce cas, la ligne indiquée fait partie de notre code, et si nous allons à cette ligne, nous voyons l’appel à la macro panic!. Dans d’autres cas, l’appel à panic! pourrait se trouver dans du code que notre code appelle, et le nom de fichier et le numéro de ligne signalés par le message d’erreur correspondront au code de quelqu’un d’autre où la macro panic! est appelée, et non à la ligne de notre code qui a finalement conduit à l’appel de panic!.
Nous pouvons utiliser la trace d’appels (backtrace) des fonctions d’où provient l’appel à panic! pour déterminer la partie de notre code qui cause le problème. Pour comprendre comment utiliser une trace d’appels de panic!, examinons un autre exemple et voyons ce qui se passe lorsqu’un appel à panic! provient d’une bibliothèque à cause d’un bogue dans notre code plutôt que de notre code appelant directement la macro. L’encart 9-1 contient du code qui tente d’accéder à un index dans un vecteur au-delà de la plage des index valides.
fn main() {
let v = vec![1, 2, 3];
v[99];
}
panic!Ici, nous tentons d’accéder au 100e élément de notre vecteur (qui se trouve à l’index 99 car l’indexation commence à zéro), mais le vecteur n’a que trois éléments. Dans cette situation, Rust va paniquer. L’utilisation de [] est censée renvoyer un élément, mais si vous passez un index invalide, il n’y à aucun élément que Rust pourrait renvoyer ici qui serait correct.
En C, tenter de lire au-delà de la fin d’une structure de données est un comportement indéfini. Vous pourriez obtenir n’importe quelle valeur se trouvant à l’emplacement mémoire qui correspondrait à cet élément dans la structure de données, même si la mémoire n’appartient pas à cette structure. Cela s’appelle un buffer overread (dépassement de lecture de tampon) et peut conduire à des vulnérabilités de sécurité si un attaquant est capable de manipuler l’index de manière à lire des données auxquelles il ne devrait pas avoir accès, stockées après la structure de données.
Pour protéger votre programme de ce type de vulnérabilité, si vous essayez de lire un élément à un index qui n’existe pas, Rust arrêtera l’exécution et refusera de continuer. Essayons pour voir : console {{#include ../listings/ch09-error-handling/listing-09-01/output.txt}}
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/panic`
thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Cette erreur pointe vers la ligne 4 de notre fichier main.rs où nous tentons d’accéder à l’index 99 du vecteur v.
La ligne note: nous indique que nous pouvons définir la variable d’environnement RUST_BACKTRACE pour obtenir une trace d’appels montrant exactement ce qui s’est passé pour provoquer l’erreur. Une trace d’appels (backtrace) est une liste de toutes les fonctions qui ont été appelées pour arriver à ce point. Les traces d’appels en Rust fonctionnent comme dans les autres langages : la clé pour lire la trace d’appels est de commencer par le haut et de lire jusqu’à ce que vous voyiez des fichiers que vous avez écrits. C’est l’endroit où le problème est né. Les lignes au-dessus de cet endroit sont du code que votre code a appelé ; les lignes en dessous sont du code qui a appelé votre code. Ces lignes avant et après peuvent inclure du code du noyau de Rust, du code de la bibliothèque standard ou des crates que vous utilisez. Essayons d’obtenir une trace d’appels en définissant la variable d’environnement RUST_BACKTRACE à n’importe quelle valeur sauf 0. L’encart 9-2 montre une sortie similaire à ce que vous verrez.
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
stack backtrace:
0: rust_begin_unwind
at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/std/src/panicking.rs:692:5
1: core::panicking::panic_fmt
at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:75:14
2: core::panicking::panic_bounds_check
at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:273:5
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:274:10
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:16:9
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:3361:9
6: panic::main
at ./src/main.rs:4:6
7: core::ops::function::FnOnce::call_once
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
panic! displayed when the environment variable RUST_BACKTRACE is setCela fait beaucoup de sortie ! La sortie exacte que vous verrez peut différer selon votre système d’exploitation et votre version de Rust. Pour obtenir des traces d’appels avec ces informations, les symboles de débogage doivent être activés. Les symboles de débogage sont activés par défaut lorsque vous utilisez cargo build ou cargo run sans le drapeau --release, comme c’est le cas ici.
Dans la sortie de l’encart 9-2, la ligne 6 de la trace d’appels pointe vers la ligne de notre projet qui cause le problème : la ligne 4 de src/main.rs. Si nous ne voulons pas que notre programme panique, nous devrions commencer notre investigation à l’emplacement indiqué par la première ligne mentionnant un fichier que nous avons écrit. Dans l’encart 9-1, où nous avons délibérément écrit du code qui paniquerait, la façon de corriger le panic est de ne pas demander un élément au-delà de la plage des index du vecteur. Lorsque votre code paniquera à l’avenir, vous devrez déterminer quelle action le code effectue avec quelles valeurs pour provoquer le panic et ce que le code devrait faire à la place.
Nous reviendrons sur panic! et sur les cas où nous devrions ou ne devrions pas utiliser panic! pour gérer les conditions d’erreur dans la section [« Utiliser panic! ou ne pas utiliser panic! »][to-panic-or-not-to-panic] plus loin dans ce chapitre. Ensuite, nous verrons comment récupérer d’une erreur en utilisant Result.