Что такое эквивалент Rust для оператора try-catch?

#error-handling #rust #try-catch

#обработка ошибок #Ржавчина #try-catch

Вопрос:

Возможно ли обрабатывать несколько разных ошибок одновременно, а не по отдельности в Rust без использования дополнительных функций? Короче говоря: что такое эквивалент Rust для оператора try-catch?

Подобная функция (первоклассная обработка ошибок с ? помощью and 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-cache с try-catch внутри других try-catch внутри циклов.

Ответ №1:

В Rust нет инструкции try catch. Самый близкий подход — ? оператор.

Однако вам не нужно создавать функцию и match оператор, чтобы разрешить ее в конце. Вы можете определить замыкание в своей области и использовать ? operator внутри замыкания. Затем броски удерживаются в возвращаемом значении закрытия, и вы можете перехватить это где угодно, например, следующим образом:

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

Игровая площадка

Возможно ли обрабатывать несколько разных ошибок одновременно, а не по отдельности в Rust без использования дополнительных функций?

В настоящее время в Rust в основном рекомендуется использовать ящик для управления ошибками.

В качестве альтернативы в Rust есть ящик для управления ошибками. Используя Failure , вы можете объединять, преобразовывать, объединять ошибки. После преобразования типов ошибок в один общий тип вы можете легко их перехватить (обработать).

Комментарии:

1. Просто для включения, но failure это не единственный ящик, который помогает в управлении ошибками. Их много, каждый с разным фокусом.

2. Обратите внимание, что ваше выражение закрытия — это именно то, в качестве чего try предназначен блок .

3. Наиболее рекомендуемым в настоящее время является anyhow .

4. @rsalmei, спасибо, что указали на новейший обновленный ящик для этого, я также отредактировал свой ответ 😉

5. Для моих целей эта настройка оказалась более полезной: play.rust-lang.org /…

Ответ №2:

Result s в Rust можно связать с помощью and_then . Так что вы можете сделать это:

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

игровая площадка

Ответ №3:

Существует также нестабильная функция, называемая 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,
}
 

Ответ №4:

Я думаю match , что выражение эквивалентно try/catch

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

Мы пытаемся получить отчет о погоде из api, если наш запрос не выполняется, обрабатываем ошибку, в противном случае отображается результат.

Ответ №5:

Не уверен, что это считается идиоматическим 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: amp;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");
    })
}
 

Ответ №6:

Концепция try and except используется в крайне расплывчатых терминах. Поскольку Rust является строго типизированным языком, пользователь должен написать свои собственные методы обработки ошибок, полагаясь на предоставленные Option<T> Result<T, E> перечисления и или определяя свои собственные привычные перечисления.

Смотрите Здесь для более подробного чтения для обработки ошибок с использованием перечислений.

try Макрос устарел и был заменен ? оператором, что упрощает организацию и очистку обработки ошибок, поскольку это может привести к беспорядку. Основное применение ? оператора будет заключаться в том, что он позволяет реализовать функцию From для Result<T, E> ‘s Err(E) variant .

Вот простой пример:

 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(())
}
 

Комментарии:

1. try Ключевое слово зарезервировано для будущего использования, не является устаревшим и имеет мало связи с (устаревшим) try! макрокомандой, которая была заменена ? оператором.