Rust中与try-catch语句等效的是什么?

60
在Rust中,是否可以一次处理多个不同的错误而不是单独处理它们,而且不使用额外的函数?简而言之:Rust中try-catch语句的等效语句是什么?
像这样的功能(使用?catch进行一流的错误处理)在2016年提出过,但我无法确定其结果以及2019年解决此问题的方案可能是什么。
例如,像这样做:
try {
    do_step_1()?;
    do_step_2()?;
    do_step_3()?;
    // etc
} catch {
    alert_user("Failed to perform necessary steps");
}

改为:

match do_steps() {
    Ok(_) => (),
    _ => alert_user("Failed to perform necessary steps")
}

// Additional function:
fn do_steps() -> Result<(), Error>{
    do_step_1()?;
    do_step_2()?;
    do_step_3()?;
    // etc
    Ok(())
}

我的程序有一个函数,它检查注册表中的不同位置以获取不同的数据值并返回一些聚合数据。它需要使用许多这些try-catch语句,其中包含循环内部的try-catch。

8个回答

64

Rust中没有try-catch语句。最接近的方法是使用?运算符。

然而,你不必创建一个函数和match语句来解决它。你可以在作用域中定义一个闭包,并在闭包内使用?运算符。这样,错误被保存在闭包返回值中,你可以在需要时捕获它们,如下所示:

fn main() {
    let do_steps = || -> Result<(), MyError> {
        do_step_1()?;
        do_step_2()?;
        do_step_3()?;
        Ok(())
    };

    if let Err(_err) = do_steps() {
        println!("Failed to perform necessary steps");
    }
}

Playground

Rust中是否有一种方法可以同时处理多种不同的错误,而不是单独处理每个错误,而无需使用额外的函数?

现在在Rust中,大多数人推荐使用anyhow crate来管理错误。

另外,还可以使用failure crate来管理错误。使用Failure,您可以链接、转换、连接这些错误。将错误类型转换为一个通用类型后,您可以轻松地捕获(处理)它。


2
只是想插一句话,但 failure 并不是唯一帮助处理错误的 crate。有许多其他的 crate,每个都有不同的重点。 - Shepmaster
7
请注意,您的闭包表达式恰好就是try块的预期用法 - Shepmaster
目前最推荐的是 anyhow - rsalmei
@rsalmei,感谢您指出最新更新的板条箱,我也已经编辑了我的答案 ;) - Akiner Alkan
显示剩余2条评论

32

在 Rust 中,可以使用 and_then 链接 Result。因此,您可以这样做:

if let Err(e) = do_step_1().and_then(do_step_2).and_then(do_step_3) {
    println!("Failed to perform necessary steps");
}

或者如果您想要一种更紧凑的语法,您可以使用宏来实现:

macro_rules! attempt { // `try` is a reserved keyword
   (@recurse ($a:expr) { } catch ($e:ident) $b:block) => {
      if let Err ($e) = $a $b
   };
   (@recurse ($a:expr) { $e:expr; $($tail:tt)* } $($handler:tt)*) => {
      attempt!{@recurse ($a.and_then (|_| $e)) { $($tail)* } $($handler)*}
   };
   ({ $e:expr; $($tail:tt)* } $($handler:tt)*) => {
      attempt!{@recurse ($e) { $($tail)* } $($handler)* }
   };
}

attempt!{{
   do_step1();
   do_step2();
   do_step3();
} catch (e) {
   println!("Failed to perform necessary steps: {}", e);
}}

playground


我认为这是最好的解决方案。 - kodmanyagha

13

还有一个不稳定的功能叫做try_blocks (https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html, https://github.com/rust-lang/rust/issues/31436)

使用示例:

#![feature(try_blocks)]

fn main() {
    // you need to define the result type explicitly
    let result: Result<(), Error> = try {
        do_step_1()?;
        do_step_2()?;
        do_step_3()?;
    };

    if let Err(e) = result {
        println!("Failed to perform necessary steps, ({:?})", e);
    }
}

fn do_step_1() -> Result<(), Error> { Ok(()) }
fn do_step_2() -> Result<(), Error> { Ok(()) }
fn do_step_3() -> Result<(), Error> { Err(Error::SomeError) }

#[derive(Debug)]
enum Error {
    SomeError,
}

6

不确定这是否被认为是惯用的 Rust,但你可以使用匿名闭包来实现类似于 try/catch 的语法:

fn do_step_1() -> Result<(), String> { Ok(()) }
fn do_step_2() -> Result<(), String> { Err("error at step 2".to_string()) }
fn do_step_3() -> Result<(), String> { Ok(()) }
fn alert_user(s: &str) { println!("{}", s); }

fn main() {
    (|| {
        do_step_1()?;
        do_step_2()?;
        do_step_3()?;
        Ok(())
    })().unwrap_or_else(|_err: String| {
        alert_user("Failed to perform the necessary steps");
    })
}

你能否也加上 finally 块? - chendu
@chendu 只需将代码放在此结构之后。try 和 catch 部分内的代码不能导致外部函数提前退出,因此无需特殊的 finally 块处理。 - Macil

2

我认为match表达式相当于try/catch

match get_weather(location) {
   Ok(report) => {
                  display_weather(location, &report);
                 }
   Err(err) => {
                 println!("error querying the weather: {}", err);
                 // or write a better logic
   }
}

我们尝试从API获取天气报告,如果请求失败,则处理错误,否则显示结果。

不是等价的,但这是 Rust 的做法。如果 try 块中的任何语句抛出异常,它将流向其 catch 块。在 Rust 中,使用这种方法,我们需要检查每个语句是否有 Err - lmat - Reinstate Monica

0
值得注意的是,在异步上下文中,异步块(即async {})的工作方式与try-catch完全相同,并且这在稳定版的Rust中也适用。它会捕获返回值,包括通过?传播的返回值,就像函数和闭包一样:
#[tokio::main]
async fn main() {
    let res = async {
        None?;
        Some(1)
    };
    assert_eq!(res.await, None);
}

0

库函数返回不同的错误类型,会让我们的代码更加复杂,特别是当我们尝试设计函数返回一个 Result 时。

我建议使用 ok(),它将 Result 转换为 Option(将潜在的 Err 转换为 None),再配合 ? 运算符,它会解析出有效的值,否则就会以 None 结束我们的函数。

显然的缺点是,我们失去了发生了什么错误的知识。

值得指出的是,Rust 不需要 finally 子句,因为清理代码总是会执行,即使 ? 失败了。

还要注意的是,在 main() 函数中不能使用 ?。在那里,我们需要使用 unwrap(),如果出现错误,则调用 panic()

为了展示这一点,请考虑下面读取 JSON 文件并解析处理不同类型错误的代码。

use std::fs;
use serde_json::{Value};

// --------------------------------------------------------
// file_name: Text -> () -> Some( ObjectJson ) | None
// --------------------------------------------------------
fn read_file( file_name: String ) -> Option<Value> {

    let contents : String = fs::read_to_string( file_name ).ok() ? ;  // read the file

    let obj : Value = serde_json::from_str( & contents ).ok() ? ; // convert the text into a JSON object

    Some( obj )

} // ()

// --------------------------------------------------------
// --------------------------------------------------------
fn main() {

    let json_obj = read_file( "data.json".to_string() ).unwrap();

    println!( "{:}", json_obj[0] );

}

-2

tryexcept 的概念用语极其模糊。由于 Rust 是一种强类型语言,用户必须编写自己的方法来处理错误,可以依靠提供的 Option<T>Result<T, E> 枚举或定义自己习惯的枚举。

请查看 此处 以深入了解使用枚举进行错误处理。

try 宏已弃用,并已被 ? 运算符替换,该运算符使错误处理更易于组织和清理,因为可能会变得混乱。 ? 运算符的主要用途是允许你为 Result<T, E>Err(E) 变体实现 From 特质。

这是一个基本示例:

use std::num::ParseIntError;

//  Custom error-based enum with a single example
#[derive(Debug)]
enum Error {
    ParseIntError(ParseIntError),
    //  Other errors...
}

//  Then implement the `From` trait for each error so that the `?` operator knows what to do for each specified error.

impl From<ParseIntError> for Error {
    fn from(error: ParseIntError) -> Self {
        Self::ParseIntError(error)
    }
}

//  When using the `?` try operator, if the `Result` is an `Err` then it will basically act as `return Err(E)` returning that error value out to the current scope.  If it is `Ok(T)`, it will simply unwrap the variant.

fn main() -> Result<(), Error> {
    //  This will return the value `69` as a `u8` type
    let parsed_value_1 = "69".parse::<u8>()?;
    println!("{}", parsed_value_1);

    //  Since parsing fails here, a `ParseIntError` will be returned to the current function.  *Since the scope is the `main` function, it will automatically print the error after panicking.
    let parsed_value_2 = "poop".parse::<u8>()?;

    //  Unreachable code
    println!("{}", parsed_value_2);
    Ok(())
}

3
try 关键字保留以备将来使用,未被弃用,并且与(已弃用的)try! 宏几乎没有关联,后者已被 ? 运算符取代。 - L. F.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接