是否有一种紧凑而惯用的方式来打印错误并返回而不返回错误?

时间:2018-01-15 09:01:46

标签: rust

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

我发现自己正在复制和粘贴这样的东西:

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

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

这是否有简写?我知道? syntax,但是传播的是if (v == null) { Console.WriteLine("Error decoding json!"); return; } {{3}}。我不觉得传播会对场景有所帮助,因为在上述场景中出现错误时需要稍微不同的处理。这是因为处理中的特定差异属于这里,而不是堆栈。

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

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

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

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

这两者都比Rust更简洁。

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

SOME_URL/{screenSize}/{type}

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

2 个答案:

答案 0 :(得分:3)

作为自定义macro_rule的替代方法,您还可以使用? Option<T>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();
}

答案 1 :(得分:3)

  

如果在上述情况中出现错误时需要稍微不同的处理,我不认为传播会对场景有所帮助。这是因为处理中的特定差异属于这里,而不是堆栈。

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

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

为每种错误类型Display创建一个实现From<E>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 From s,由于自定义错误类型,这是必需的。如果唯一能用错误完成的事情是log并忽略它,那么自定义错误类型并不是特别有用 - 唯一真正需要返回的是错误消息本身。

在这种情况下,让try_poll代替Result<(), String>,并使用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 Language可以将String作为错误类型说明:

  

经验法则是定义您自己的错误类型,但是String错误类型会在紧要关头进行,特别是在您编写应用程序时。如果您正在编写库,则应首选定义您自己的错误类型,这样您就不会不必要地从调用者中删除选项。