Развернуть или продолжить в цикле

Учти это:

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

Ссылка на игровую площадку, которая использует skip_fail, чтобы распечатать числа, кратные 1, 2 и 3, и вывести ошибку, когда одно из делений будет усечено.

Опять же, я считаю, что с помощью ? в отдельной функции, и возвращая 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;
    });
}
Другие вопросы по тегам