打开或继续循环

时间:2018-04-11 22:11:19

标签: rust

考虑一下:

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(),但是上面的代码是否有快捷方式?&d; d我认为,避免复制粘贴Err / Ok分支,这是典型的情况吗?类似if let的东西也会返回ok值。

4 个答案:

答案 0 :(得分:7)

如果您要经常“解包或继续”结果,请考虑将该逻辑封装在单独的函数中。有了它,您可以利用?语法来引发函数之外的错误。然后可以将循环的流逻辑写在一个地方(尽管此时,您可能不再需要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(())
}

答案 1 :(得分:5)

虽然我认为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());

Playground link which uses skip_fail to print out numbers divisible by 1, 2, and 3, and print an error when one of the divisions would truncate.

同样,我相信在一个单独的函数中使用?,并且如果没有任何失败则返回Ok(end_result)可能是最惯用的解决方案,所以如果你可以使用那个答案,你可能应该这样做。 / p>

答案 2 :(得分:4)

如果您必须将多个Ok链接在一起,则需要在下一个操作中使用一个Ok的值,而不必关心 where < / em>在链中出现错误,请考虑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);
    }
}

somethingsomething_elseanother_thingone_more都返回某种形式的Result。即使此示例删除continue语句,and_thenResult类型为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);
    }
}

(注意函数上没有括号,表示它们被用作可调用对象而不是它们的返回值)

答案 3 :(得分:2)

如果您愿意使用不稳定的功能,可以为此使用try块:

#![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)来完成此操作而不会产生任何不稳定的特征。这是MutantOctopus在解决方案注释中提出的E_net4解决方案的修改。

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