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]; }
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.
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
.