【问题标题】:Is there a shortcut to unwrap or continue in a loop?是否有打开或继续循环的快捷方式?
【发布时间】:2018-09-21 22:23:21
【问题描述】:

考虑一下:

loop {
    let data = match something() {
        Err(err) => {
            warn!("An error: {}; skipped.", err);
            continue;
        },
        Ok(x) => x
    };

    let data2 = match something_else() {
        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 值。

【问题讨论】:

  • 你可以去掉let datacontinue,然后把“其他东西”放在Ok(x) =>的情况下。
  • @sepp2k 是的,但是当循环中有多个结果操作时,这将很快变成大量嵌套代码。写一个宏会是一种出路吗?
  • “避免在此复制粘贴 Err/Ok 分支”这里复制粘贴的是什么?
  • 你错过了像Result这样的结构点,这个强制代码检查错误,如果有什么可以绕过这个,那有什么意义?这种结构在 Rust 中非常常见,以至于使用? 来避免代码复制。对于仅测试或在主函数中,您可以使用.unwrap().expect()
  • 简洁地编写Results 的一种方法是使用一个理解板条箱,如map_formdo。 (注:我是map_for的作者)

标签: loops rust pattern-matching control-flow


【解决方案1】:

虽然我认为 E_net4 的答案可能是最好的答案,但我正在为后代添加一个宏,以防万一创建单独的函数并使用 ? 运算符提前返回由于某种原因是不可取的。

这是一个简单的skip_fail! 宏,当传递一个错误时,continues 是一个包含循环:

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),这可能是最惯用的解决方案,所以如果你可以使用该答案,你可能应该这样做。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 值得注意的是,如果您不想在源代码的另一部分中创建完全独立的函数,我相信您也可以使用闭包来执行此操作。虽然我不知道那看起来有多好......
    • @BHustus 确实如此,但我相信这里有一个单独的函数更容易阅读。 :)
    • 如果 do_event 返回你需要的东西,那看起来会很难看?并且您必须在检查错误后解包结果?
    • 不确定我是否理解您的问题@Sean,但不,通过? 从结果中引发错误是相当干净和惯用的,如所介绍的。在调用链的顶端,一个好的最终用户应用程序应该有一个合适的错误报告器,以人类可读的方式呈现错误。考虑查看eyremiette crates 以获取错误报告库的示例,以及此overview video on error handling in Rust
    • @Sean 如果在循环之外预期最终结果,那么可以肯定的是,可以调整顶级逻辑以使用match 而不是if let。正如另一个答案所暗示的,也可以构建一个宏来执行此操作。但是,如果只是为了继续处理该循环步骤中的数据,那么此模式希望您在 do_event 本身内扩展该逻辑。
    【解决方案3】:

    如果你必须将多个Oks链接在一起,需要在下一个操作中使用一个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);
        }
    }
    

    其中somethingsomething_elseanother_thingone_more 都返回某种形式的Result。尽管此示例删除了 continue 语句,但当 Result 的类型为 Err 时,and_then 通过短路有效地模拟了它。将跳过所有后续调用。

    您可以通过在只需要一次函数调用的语句上使用非闭包来使其更加简洁:

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

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

    【讨论】:

      【解决方案4】:

      如果您愿意使用不稳定的功能,您可以为此使用 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 在 cmets 中提到的,这可以使用立即评估的闭包(立即调用函数表达式,或简称 IIFE)在没有任何不稳定特征的情况下完成。这是MutantOctopus在解决方案cmets中提出的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)
              }
          }
      }
      

      【讨论】:

      【解决方案5】:

      您可以使用我的unwrap_or crate 来完成此操作。

      你可以用它来编写漂亮干净的代码:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-03-27
        • 2013-05-06
        • 2011-06-02
        • 1970-01-01
        • 2015-10-04
        • 2016-11-28
        • 1970-01-01
        相关资源
        最近更新 更多