English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

处理错误Rust

Rust dispose d'un mécanisme unique de traitement des situations d'exception, qui n'est pas aussi simple que le mécanisme try d'autres langages.

D'abord, deux types d'erreurs apparaissent généralement dans le programme : les erreurs réversibles et les erreurs irreversibles.

Un cas typique d'erreur réversible est une erreur d'accès aux fichiers. Si l'accès à un fichier échoue, cela peut être dû au fait qu'il est occupé, ce qui est normal, nous pouvons résoudre cela en attendant.

Mais il y a également des erreurs causées par des erreurs logiques impossibles à résoudre dans le codage, par exemple, accéder à une position en dehors de la fin de l'array.

La plupart des langages de programmation ne distinguent pas ces deux types d'erreurs et utilisent la classe Exception (exception) pour représenter les erreurs. En Rust, il n'y a pas d'Exception.

Pour les erreurs réversibles, utilisez la classe Result<T, E> pour les traiter, et pour les erreurs irreversibles, utilisez le macro panic! pour les traiter.

Erreur irreversible

Bien que ce chapitre n'ait pas introduit spécifiquement la syntaxe des macros Rust, nous avons déjà utilisé le macro println!, car ces macros sont assez simples, nous n'avons pas besoin de les maîtriser intégralement pour le moment. Nous pouvons apprendre à utiliser le macro panic! de la même manière.

fn main() {
    panic!("error occurred");
    println!("Hello, Rust");
}

Résultat de l'exécution :

thread 'main' paniced at 'error occurred', src\main.rs:3:5
note: exécuter avec `RUST_BACKTRACE=1`environment variable to display a backtrace.

Il est évident que le programme ne peut pas s'exécuter jusqu'à println!("Hello, Rust") mais s'arrête lorsque le macro panic! est appelé.

Les erreurs irreversibles entraîneront inévitablement l'arrêt fatal du programme et la fin de son exécution.

Laissez-nous observer les deux lignes de sortie d'erreur :

  • La première ligne affiche la position appelée par le macro panic! et les informations d'erreur générées.

  • La deuxième ligne est un提示, traduit en chinois, c'est "via `RUST_BACKTRACE="1`Variable d'environnement exécutée pour afficher le débogage". Nous allons maintenant présenter le débogage (backtrace).

Ensuite, dans l'exemple précédent, nous créons un terminal dans VSCode :

Définissez les variables d'environnement dans le terminal nouvellement créé (les méthodes des terminaux differents, voici deux méthodes principales) :

Si vous utilisez Windows 7 Et pour les versions de systèmes d'exploitation Windows par défaut, la ligne de commande utilisée par le terminal est Powershell, veuillez utiliser la commande suivante :

$env:RUST_BACKTRACE=1 ; cargo run

Si vous utilisez un système UNIX comme Linux ou macOS, généralement, la ligne de commande par défaut est bash, veuillez utiliser la commande suivante :

RUST_BACKTRACE=1 cargo run

Ensuite, vous verrez le texte suivant :

thread 'main' paniced at 'error occurred', src\main.rs:3:5
stack backtrace:
  ...
  11: greeting::main
             at ".\src\main.rs":3
  ...

Le débogage de pile est une autre méthode de traitement des erreurs irreversibles, qui décompose la pile d'exécution et affiche toutes les informations, puis le programme continue de s'exécuter. Les points de suspension... représentent une grande quantité d'informations d'output, dans lesquelles nous pouvons trouver les erreurs déclenchées par la macro panic!.

Erreurs récupérables

Cette conception est très similaire à celle des exceptions dans le langage de programmation Java. En fait, en langage C, nous utilisons souvent les valeurs de retour entières des fonctions pour exprimer les erreurs rencontrées, et en Rust, nous utilisons l'enumère Result<T, E> pour exprimer les exceptions en tant que valeurs de retour :

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Les fonctions qui peuvent générer des exceptions dans la bibliothèque standard de Rust ont des valeurs de retour de type Result. Par exemple : lorsque nous essayons d'ouvrir un fichier :

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => {
            println!("Le fichier ouvert avec succès.");
        },
        Err(err) => {
            println!("Échec de l'ouverture du fichier.");
        }
    }
}

Si le fichier hello.txt n'existe pas, il enverra "Échec de l'ouverture du fichier."

Bien sûr, la syntaxe if let que nous avons abordée dans la section des classes d'énumération peut simplifier les blocs de syntaxe match :

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    if let Ok(file) = f {
        println!("Le fichier ouvert avec succès.");
    } else {
        println!("Échec de l'ouverture du fichier.");
    }
}

Si vous souhaitez traiter une erreur récupérable comme une erreur non récupérable, la classe Result fournit deux méthodes : unwrap() et expect(message: &str) :

use std::fs::File;
fn main() {
    let f1 = File::open("hello.txt").unwrap();
    let f2 = File::open("hello.txt").expect("Failed to open.");
}

Ce programme est équivalent à appeler la macro panic! lorsque Result est Err. La différence réside en ce que expect peut envoyer un message d'erreur spécifié à la macro panic!.

Transmission des erreurs récupérables

Ce que nous avons parlé précédemment est la manière de traiter les erreurs reçues, mais que faire si nous voulons transmettre une erreur en écrivant nous-mêmes une fonction ?

fn f(i: i32) -> Result<i32, bool> {
    if i >= 0 { Ok(i) }
    else { Err(false) }
}
fn main() {
    let r = f(10000);
    if let Ok(v) = r {
        println!("Ok: f(-1) = {}", v);
    } else {
        println!("Err");
    }
}

Résultat de l'exécution :

Ok: f(-1) = {}", v); 10000

La fonction f est la source d'erreur dans ce programme, écrivons une fonction de transmission des erreurs g :

fn g(i: i32) -> Result<i32, bool> {
    let t = f(i);
    return match t {
        Ok(i) => Ok(i),
        Err(b) => Err(b)
    };
}

La fonction g transmet les erreurs possibles de la fonction f (ici g ne est qu'un exemple simple, en réalité, la fonction de transmission des erreurs contient généralement de nombreuses autres opérations).

Écrire ainsi est quelque peu long, en Rust, vous pouvez ajouter l'opérateur ? après l'objet Result pour transmettre directement les Err du même type :

fn f(i: i32) -> Result<i32, bool> {
    if i >= 0 { Ok(i) }
    else { Err(false) }
}
fn g(i: i32) -> Result<i32, bool> {
    let t = f(i)?;
    Ok(t) // Étant donné que t n'est pas Err, t ici est déjà i32 Type
}
fn main() {
    let r = g(10000);
    if let Ok(v) = r {
        println!("Ok: g(10000) = {}", v);
    } else {
        println!("Err");
    }
}

Résultat de l'exécution :

Ok: g(10000) = {} 10000

L'effet réel du symbole ? est deExtraire directement la valeur non exceptionnelle de la classe Result, et de renvoyer Result d'exception si une exception se produit. Par conséquent, le symbole ? n'est utilisé que pour les fonctions dont le type de retour est Result<T, E>, où le type E doit correspondre au type E traité par ?.

méthode kind

Jusqu'à présent, Rust semble ne pas avoir de syntaxe comme le bloc try pour résoudre directement les exceptions de même type qui peuvent se produire n'importe où, mais cela ne signifie pas que Rust ne peut pas le faire : nous pouvons parfaitement réaliser le bloc try dans une fonction indépendante, transmettre toutes les exceptions pour les résoudre. En réalité, c'est une méthode de programmation qui devrait être suivie par un programme bien structuré : il faut accorder de l'importance à l'intégrité des fonctions indépendantes.

Mais pour cela, il faut juger du type Err du Result, la fonction pour obtenir ce type est kind().

use std::io;
use std::io::Read;
use std::fs::File;
fn read_text_from_file(path: &str) -> Result<String, io::Error> {
    let mut f = File::open(path)?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}
fn main() {
    let str_file = read_text_from_file("hello.txt");
    match str_file {
        Ok(s) => println!("{}", s),
        Err(e) => {
            match e.kind() {
                io::ErrorKind::NotFound => {
                    println!("No such file");
                },
                _ => {
                    println!("Cannot read the file");
                }
            }
        }
    }
}

Résultat de l'exécution :

Fichier introuvable