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

Tratamento de Erros no Rust

O Rust possui um mecanismo exclusivo de tratamento de situações de exceção, que não é tão simples quanto o mecanismo try de outras linguagens.

Primeiro, geralmente há dois tipos de erros no programa: erros recoveráveis e erros irreversíveis.

Um caso típico de erro recoverável é o erro de acesso ao arquivo, se o acesso a um arquivo falhar, pode ser porque ele está sendo ocupado, o que é normal, podemos resolver isso esperando.

Mas há ainda outros erros causados por erros lógicos que não podem ser resolvidos no programa de programação, por exemplo, acessar uma posição além do final do array.

A maioria das linguagens de programação não distingue esses dois tipos de erros e os representa usando a classe Exception (exceção). No Rust, não há Exception.

Para erros recoveráveis, use a classe Result<T, E>, e para erros irreversíveis, use o macro panic!.

Erro irreversível

Este capítulo não introduziu especificamente a sintaxe de macro do Rust até agora, mas já usamos o macro println!, porque sua utilização é relativamente simples, portanto, temporariamente não é necessário dominá-la completamente, podemos aprender a usar o macro panic! da mesma maneira.

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

Resultado da Execução:

thread '3:5
note: execute com `RUST_BACKTRACE=1environment variable para exibir um backtrace.

Claramente, o programa não pode executar conforme o esperado até println!("Hello, Rust") e pára de funcionar quando o macro panic! é chamado.

Erros irreversíveis levarão necessariamente o programa a sofrer um impacto mortal e parar de funcionar.

Vamos olhar para as duas linhas de saída de erro:

  • A primeira linha escreveu a posição do macro panic! e as informações de erro emitidas.

  • A segunda linha é uma dica, traduzida para chinês é "Através de `RUST_BACKTRACE="1`Executar variável de ambiente para exibir rastreamento". Em seguida, vamos introduzir o rastreamento (backtrace).

A seguir, vamos criar um terminal no VSCode com base no exemplo anterior:

Defina a variável de ambiente no terminal recém-criado (os métodos de terminal diferentes variam, aqui apresentamos dois métodos principais):

Se você estiver no Windows 7 E nas versões de sistemas operacionais Windows mais recentes, o comando de linha padrão usado é Powershell, por favor, use o seguinte comando:

$env:RUST_BACKTRACE=1 ; cargo run

Se você estiver usando um sistema UNIX como Linux ou macOS, geralmente o shell de comando padrão é bash, por favor, use o seguinte comando:

RUST_BACKTRACE=1 cargo run

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

O rastreamento de pilha é outra maneira de lidar com erros não recuperáveis, que expande a pilha de execução e exibe todas as informações, após o que o programa ainda sai. Os pontos de interrogação (...) representam uma grande quantidade de informações de saída, onde podemos encontrar erros acionados pela macro panic!.

Erros recuperáveis

Este conceito é muito semelhante ao de exceções na linguagem de programação Java. Na verdade, em C, frequentemente configuramos o valor de retorno de uma função como um inteiro para expressar erros encontrados pela função, no Rust, usamos o enum Result<T, E> como valor de retorno para expressar exceções:

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

As funções que podem gerar exceções na biblioteca padrão do Rust retornam valores do tipo Result. Por exemplo: quando tentamos abrir um arquivo:

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => {
            println!("Arquivo aberto com sucesso.");
        },
        Err(err) => {
            println!("Falha ao abrir o arquivo.");
        }
    }
}

Se o arquivo hello.txt não existir, ele impressará "Falha ao abrir o arquivo."

Claro, a sintaxe if let, que mencionamos no capítulo sobre classes enumeradas, pode simplificar o bloco de sintaxe match:

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    if let Ok(file) = f {
        println!("Arquivo aberto com sucesso.");
    } else {
        println!("Falha ao abrir o arquivo.");
    }
}

如果想使一个可恢复错误按不可恢复错误处理,Result 类提供了两个办法:unwrap() 和 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.");
}

这段程序相当于在 Result 为 Err 时调用 panic! 宏。两者的区别在于 expect 能够向 panic! 宏发送一段指定的错误信息。

可恢复的错误的传递

之前所讲的是接收到错误的处理方式,但是如果我们自己编写一个函数在遇到错误时想传递出去怎么办呢?

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");
    }
}

Resultado da Execução:

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

这段程序中函数 f 是错误的根源,现在我们再写一个传递错误的函数 g :

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

函数 g 传递了函数 f 可能出现的错误(这里的 g 只是一个简单的实例,实际上传递错误的函数一般还包含很多其它操作)。

这样写有些冗长,Rust 中可以在 Result 对象后添加 ? 操作符将同类的 Err 直接传递出去:

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) // 因为确定 t 不是 Err, t 在这里已经是 i32 类型
}
fn main() {
    let r = g(10000);
    if let Ok(v) = r {
        println!("Ok: g(10000) = {}", v);
    } else {
        println!("Err");
    }
}

Resultado da Execução:

Ok: g(10000) = {} 10000

? 符的实际作用是将 Result 类非异常的值直接取出,如果有异常就将异常 Result 返回出去。所以,? 符仅用于返回值类型为 Result<T, E> 的函数,其中 E 类型必须和 ? 所处理的 Result 的 E 类型一致。

método kind

Até agora, parece que o Rust não tem uma sintaxe como o bloco try para resolver diretamente todas as exceções que ocorrem em qualquer lugar, mas isso não significa que o Rust não pode implementar: podemos implementar o bloco try em uma função independente, transmitindo todas as exceções para serem resolvidas. Na verdade, isso é um bom método de programação de programas bem estruturados: deve-se prestar atenção à integridade das funções independentes.

Mas para isso, é necessário julgar o tipo Err do Result, e a função para obter o tipo Err é 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");
                }
            }
        }
    }
}

Resultado da Execução:

Arquivo não encontrado