Развернуть или продолжить в цикле
Учти это:
loop {
let data = match something() {
Err(err) => {
warn!("An error: {}; skipped.", err);
continue;
},
Ok(x) => x
};
let data2 = match somethingElse() {
Err(err) => {
warn!("An error: {}; skipped.", err);
continue;
},
Ok(x) => x
};
// and so on
}
Если мне не нужно присваивать значение ok data
Я бы использовал if let Err(err) = something()
, но есть ли ярлык к приведенному выше коду, который позволит избежать вставки копий веток Err/Ok в этом, я думаю, типичном сценарии? Что-то вроде if let
это также вернуло бы значение ok.
6 ответов
Хотя я думаю, что ответ E_net4, вероятно, самый лучший, я добавляю макрос для потомков в случае создания отдельной функции и раннего возврата с ?
Оператор по какой-то причине нежелателен.
Вот простой skip_fail!
макрос, который continue
Содержит цикл, когда передается ошибка:
macro_rules! skip_fail {
($res:expr) => {
match $res {
Ok(val) => val,
Err(e) => {
warn!("An error: {}; skipped.", e);
continue;
}
}
};
}
Этот макрос можно использовать как let ok_value = skip_fail!(do_something());
Опять же, я считаю, что с помощью ?
в отдельной функции, и возвращая Ok(end_result)
если ничего не выходит из строя, это, вероятно, самое идиоматическое решение, поэтому, если вы можете использовать этот ответ, вам, вероятно, следует
Руст 1.65.0 добавитьlet-else
заявления. Таким образом, вы можете написать это так:
loop {
let data = something()
let Ok(ok_data) = data else {
warn!("skipped.");
continue;
};
// ok_data is available here
let Ok(data2) = something_else(ok_data) else {
continue;
};
// and so on
}
Но у вас не будет доступа кerr
переменная
Если вы собираетесь "развернуть или продолжить" результаты часто, подумайте об инкапсуляции этой логики в отдельную функцию. С его помощью вы можете воспользоваться ?
синтаксис, чтобы вызвать ошибки из функции. Логика потока цикла может быть записана в одном месте (хотя на этом этапе вам может больше не понадобиться continue
).
loop {
if let Err(err) = do_event() {
warn!("An error: {}; skipped.", err);
// continue; // you also don't need this
}
}
fn do_event() -> Result<(), YourErrorType> {
let data = do_something()?; //
let x = something_more()?; // error propagation!
Ok(())
}
Если вам нужно объединить несколько Ok
вместе, нужно использовать один Ok
значение в следующей операции, и не волнует, где в цепочке возникает ошибка, рассмотрим and_then
:
loop {
let outcome = something()
.and_then(|a| something_else(a))
.and_then(|a| another_thing(a))
.and_then(|a| {
let b = a + salt;
one_more(b)
});
if let Err(e) = outcome {
warn!("An error: {}; skipped.", e);
}
}
куда something
, something_else
, another_thing
а также one_more
все возвращают ту или иную форму Result
, Хотя этот пример удаляет continue
заявление, and_then
эффективно имитирует его путем короткого замыкания, когда Result
имеет тип Err
, Все дальнейшие звонки по линии будут пропущены.
Вы можете сделать это еще более кратким, используя незакрытие операторов, которые требуют только одного вызова функции:
loop {
let outcome = something()
.and_then(something_else)
.and_then(another_thing)
.and_then(|a| one_more(a + salt));
if let Err(e) = outcome {
warn!("An error: {}; skipped.", e);
}
}
(Обратите внимание на отсутствие скобок в функциях, которые указывают, что они используются как вызываемые объекты, а не принимают возвращаемое значение)
Если вы готовы использовать нестабильные функции, вы можете использовать для этого блок попытки:
#![feature(try_blocks)]
pub fn something() -> Result<String, String> {
Err(String::from("Badness"))
}
pub fn something_else() -> Result<String, String> {
Ok(String::from("Ok"))
}
pub fn main() {
loop {
let result: Result<(), String> = try {
let data = something()?;
let data2 = something_else()?;
};
if let Err(e) = result {
println!("An error: {}; skipped.", e)
}
}
}
Как упоминает shepmaster в комментариях, это можно сделать без каких-либо нестабильных функций, используя закрытие, которое немедленно оценивается (выражение немедленного вызова функции, или сокращенно IIFE). Это модификация решения E_net4, предложенная MutantOctopus в комментариях к решению.
pub fn something() -> Result<String, String> {
Err(String::from("Badness"))
}
pub fn something_else() -> Result<String, String> {
Ok(String::from("Ok"))
}
pub fn main() {
loop {
let result: Result<(), String> = (|| {
let data = something()?;
let data2 = something_else()?;
Ok(())
})();
if let Err(e) = result {
println!("An error: {}; skipped.", e)
}
}
}
Для этого вы можете использовать мой ящик unwrap_or .
Вы можете использовать его для создания красивого и чистого кода:
unwrap_or_ok!(callable(&mut param), _, return);
loop {
let data = unwrap_ok_or!(something(), err, {
warn!("An error: {}; skipped.", err);
continue;
});
let data2 = unwrap_ok_or!(somethingElse(), err, {
warn!("An error: {}; skipped.", err);
continue;
});
}