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

Rust 错误处理

Rust tiene un mecanismo único para manejar situaciones de excepción, que no es tan simple como el mecanismo try de otros lenguajes.

Primero, en el programa generalmente hay dos tipos de errores: errores recuperables y errores no recuperables.

Un caso典型 de error recuperable es el error de acceso a archivo, si el acceso a un archivo falla, podría ser porque está siendo ocupado, lo que es normal, podemos resolverlo esperando.

Pero también hay errores causados por errores lógicos que no se pueden resolver en la programación, como acceder a una posición fuera del final del array.

La mayoría de los lenguajes de programación no distinguen entre estos dos tipos de errores y los representan con la clase Exception (excepción). En Rust no hay Exception.

Para los errores recuperables se utiliza la clase Result<T, E>, y para los errores no recuperables se utiliza el macro panic!.

Error no recuperable

Este capítulo no ha presentado específicamente la sintaxis de los macros de Rust, pero ya se ha utilizado el macro println! , ya que su uso es bastante simple, por lo que por ahora no es necesario dominarlo completamente, podemos aprender a usar el macro panic! de la misma manera.

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

运行结果:

thread 'main' panicó en 'error occurred', src\main.rs:3:5
nota: ejecutar con `RUST_BACKTRACE=1`environment variable para mostrar un backtrace.

Obviamente, el programa no puede ejecutarse como se esperaba hasta println!("Hello, Rust") , sino que se detiene cuando se llama al macro panic!.

Los errores no recuperables siempre llevarán a que el programa reciba un golpe mortal y se detenga su ejecución.

Vamos a observar las dos líneas de salida de error:

  • La primera línea muestra la ubicación del macro panic! y la información de error que produce.

  • La segunda línea es una提示,traducida al chino es "a través de `RUST_BACKTRACE="1`` Ejecutar la variable de entorno para mostrar la pila de despliegue ". Siguientes, presentaremos el despliegue (backtrace)."

Siguiendo el ejemplo anterior, creamos un terminal nuevo en VSCode:

Configura la variable de entorno en el terminal recién creado (los métodos de terminal diferentes, aquí se presentan dos métodos principales):

Si estás en Windows 7 En versiones de sistemas operativos Windows anteriores, el comando de línea de terminal por defecto es Powershell, por favor usa el siguiente comando:

$env:RUST_BACKTRACE=1 ; cargo run

Si estás utilizando un sistema UNIX como Linux o macOS, generalmente se utiliza el shell de comandos bash por defecto, por favor usa el siguiente comando:

RUST_BACKTRACE=1 cargo run

Luego, verás el siguiente texto:

thread 'main' panicó en 'error occurred', src\main.rs:3:5
stack backtrace:
  ...
  11: greeting::main
             en .\src\main.rs:3
  ...

El desplegue de pila es otra forma de manejar errores no recuperables, que despliega la pila de ejecución y muestra toda la información, después de lo cual el programa seguirá saliendo. Los puntos suspensivos (...) representan una gran cantidad de información de salida, podemos encontrar el error desencadenado por el macro panic!.

Errores recuperables

Este concepto es muy similar al de la lenguaje de programación Java. De hecho, en el lenguaje C a menudo configuramos el valor de retorno de las funciones como enteros para expresar los errores que encuentra la función, en Rust se utiliza el enumerado Result<T, E> como valor de retorno para expresar excepciones:

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

En la biblioteca estándar de Rust, los valores de retorno de las funciones que pueden generar excepciones son del tipo Result. Por ejemplo: cuando intentamos abrir un archivo:

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => {
            println!("El archivo se abrió con éxito.");
        },
        Err(err) => {
            println!("No se pudo abrir el archivo.");
        }
    }
}

Si el archivo hello.txt no existe, se imprimirá "No se pudo abrir el archivo."

Por supuesto, la sintaxis if let que hablamos en el capítulo sobre la enumeración de clases puede simplificar el bloque de sintaxis match:

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    if let Ok(file) = f {
        println!("El archivo se abrió con éxito.");
    } else {
        println!("No se pudo abrir el archivo.");
    }
}

如果想使一个可恢复错误按不可恢复错误处理,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");
    }
}

运行结果:

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

运行结果:

Ok: g(10000) = {} 10000

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

kind 方法

到此为止,Rust 似乎没有像 try 块一样可以令任何位置发生的同类异常都直接得到相同的解决的语法,但这样并不意味着 Rust 实现不了:我们完全可以把 try 块在独立的函数中实现,将所有的异常都传递出去解决。实际上这才是一个分化良好的程序应当遵循的编程方法:应该注重独立功能的完整性。

但是这样需要判断 Result 的 Err 类型,获取 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");
                }
            }
        }
    }
}

运行结果:

No such file