Можно ли обрабатывать несколько разных ошибок одновременно, а не по отдельности в 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-cache с try-catch внутри других операторов try-catch внутри циклов.

В 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");
}
}
Is it possible to handle multiple different errors at once instead of individually in Rust without using additional functions?
Существует крейт во всяком случае для управления ошибками в Rust, наиболее рекомендуемый в настоящее время.
В качестве альтернативы существует крейт отказ для управления ошибками в Rust. Используя Failure, вы можете связать, преобразовать, объединить ошибки. После преобразования типов ошибок в один общий тип вы можете легко их поймать (обработать).
Обратите внимание, что ваше выражение замыкания точно равно для чего предназначен блок try.
В настоящее время наиболее рекомендуемым является anyhow.
@rsalmei, спасибо, что указали на новейший обновленный ящик для этого, я также отредактировал свой ответ;)
Results в 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);
}}
Также есть нестабильная функция под названием 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,
}
Понятия try и except используются крайне расплывчато. Поскольку Rust является строго типизированным языком, пользователь должен написать свои собственные методы обработки ошибок, полагаясь на предоставленные перечисления Option<T> и Result<T, E> или определяя свои собственные привычные перечисления.
См. здесь для более подробного ознакомления с обработкой ошибок с помощью перечислений.
Макрос try устарел и был заменен оператором ?, который упрощает организацию и очистку обработки ошибок, потому что он может запутаться. Основное использование оператора ? заключается в том, что он позволяет вам реализовать трейт От для варианта Result<T, E>'s Err(E).
Вот простой пример:
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(())
}
Ключевое слово try зарезервировано для использования в будущем, не устарело и мало связано с (устаревшим) макросом try!, который был заменен оператором ?.
Просто чтобы вмешаться, но
failure— не единственный ящик, который помогает с управлением ошибками. Их много, каждый с разной направленностью.