【问题标题】:Is there a compact and idiomatic way to print an error and return without returning the error?是否有一种紧凑且惯用的方式来打印错误并返回而不返回错误?
【发布时间】:2018-01-15 09:01:45
【问题描述】:

我正在编写一个函数,该函数将在无限循环中调用,并且仅在从 Web 服务获取格式良好的数据时执行某些操作。如果服务宕机,返回非json,或者返回我们不理解的json,该函数应该只记录错误并返回(暂停后再次调用)。

我发现自己在复制和粘贴这样的内容:

let v = match v {
    Ok(data) => data,
    Err(error) => {
        println!("Error decoding json: {:?}", error);
        return;
    }
};

错误匹配器的主体每次都会不同。有时它是恐慌,有时它有不同的消息,有时error 的元素可以进一步分解以形成更好的消息,但结构的其余部分将是相同的。

这个有简写吗?我知道? syntax,但那是为了传播。当您需要稍微不同的处理以防出现上述场景中的错误时,我认为传播不会对场景有所帮助。这是因为处理方面的特殊差异就在此处,而不是在堆栈中。

我还没有用 Rust 写过很多代码,所以很可能我遗漏了一些明显的东西。

在 C# 中,上面的内容如下所示:

if (v == null)
{
  Console.WriteLine("Error decoding json!");
  return;
}

if (error != null)
{
  Console.WriteLine($"Error decoding json: {error}");
  return;
}

这两者都比 Rust 简洁得多。

如果我理解下面的 cmets,一种缩短方法是这样的:

if let Err(error) = v {
    println!("Error decoding json: {:?}", error);
    return;
}
let v = v.unwrap();

这看起来更紧凑,谢谢。这是惯用语吗?你会这样写吗?

【问题讨论】:

标签: rust


【解决方案1】:

当您需要稍微不同的处理以防出现上述场景中的错误时,我认为传播不会对场景有所帮助。这是因为处理方面的特殊差异就在此处,而不是在堆栈中。

这是自定义错误类型可以帮助解决的问题。在这种情况下,您有一个共同的行为(“记录错误”),并且您希望针对不同的值以稍微不同的方式执行此操作。将“记录错误”部分向上移动到调用者是有意义的(让我们调用函数try_poll):

loop {
    if let Err(e) = try_poll() {
        println!("{}", e);
    }
    sleep(100);
}

并为每个错误类型E创建一个实现DisplayFrom<E>的类型:

enum PollError {
    NetworkError(NetworkError),
    JsonParseError(JsonParseError),
}

impl fmt::Display for PollError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            PollError::NetworkError(ref e) => write!(f, "Error downloading file: {:?}", e),
            PollError::JsonParseError(ref e) => write!(f, "Error parsing JSON: {:?}", e),
        }
    }
}

impl From<NetworkError> for PollError {
    fn from(e: NetworkError) -> Self {
        PollError::NetworkError(e)
    }
}

impl From<JsonParseError> for PollError {
    fn from(e: JsonParseError) -> Self {
        PollError::JsonParseError(e)
    }
}

现在您可以使用? 传播错误,但调用者仍然不必关心具体是哪个错误。

fn try_poll() -> Result<(), PollError> {
    let data = try_fetch_content()?;
    let json = try_parse_json(data)?;
    println!("Parsed {:?}", json);
    Ok(())
}

(playground)


好的,我想要那个,但没有所有 From 实现。

关于此的繁琐部分是所有impl Froms,由于自定义错误类型,这些都是必需的。如果对错误唯一要做的事情就是记录并忽略它,那么自定义错误类型并不是特别有用——唯一真正需要返回的是错误消息本身。

在这种情况下,让try_poll 改为返回Result&lt;(), String&gt;,并使用Result::map_err 将每个单独的错误立即转换为错误消息,然后再使用? 传播它:

fn try_poll() -> Result<(), String> {
    let data = try_fetch_content()
        .map_err(|e| format!("Error downloading file: {:?}", e))?;
    let json = try_parse_json(data)
        .map_err(|e| format!("Error parsing JSON: {:?}", e))?;
    println!("Parsed {:?}", json);
    Ok(())
}

(playground)

first edition of The Rust Programming LanguageString 作为错误类型有这样的说法:

经验法则是定义您自己的错误类型,但String 错误类型会在紧要关头完成,尤其是在您编写应用程序时。如果您正在编写一个库,则强烈建议您定义自己的错误类型,这样您就不会不必要地从调用者那里删除选择。

【讨论】:

    【解决方案2】:

    作为自定义 macro_rule 的替代方案,您还可以使用 ?Option&lt;T&gt; 以及 Result 的特征扩展来打印错误并转换成功的值。

    Playground

    pub trait ResultOkPrintErrExt<T> {
        fn ok_or_print_err(self, msg: &str) -> Option<T>;
    }
    
    impl<T, E> ResultOkPrintErrExt<T> for Result<T, E>
    where
        E: ::std::fmt::Debug,
    {
        fn ok_or_print_err(self, msg: &str) -> Option<T> {
            match self {
                Ok(v) => Some(v),
                Err(e) => {
                    eprintln!("{}: {:?}", msg, e);
                    None
                }
            }
        }
    }
    
    fn read_input() -> Result<u32, ()> {
        // Ok(5)
        Err(())
    }
    
    fn run() -> Option<()> {
        let v: u32 = read_input().ok_or_print_err("invalid input")?;
        println!("got input: {}", v);
        Some(())
    }
    
    fn main() {
        run();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-28
      • 2010-11-04
      • 2011-09-30
      • 2017-03-01
      • 1970-01-01
      • 2017-07-17
      • 1970-01-01
      相关资源
      最近更新 更多