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

And*_*ykh 7 rust

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

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

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

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

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

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

在 C# 中,上面的代码看起来像这样:

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

或者

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

两者都比 Rust 少得多。

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

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

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

Ste*_*fan 5

作为自定义的替代方案,macro_rule您还可以使用?withOption<T>和 trait 扩展Result来打印错误并转换成功的值。

操场

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();
}
Run Code Online (Sandbox Code Playgroud)


tre*_*tcl 5

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

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

loop {
    if let Err(e) = try_poll() {
        println!("{}", e);
    }
    sleep(100);
}
Run Code Online (Sandbox Code Playgroud)

并创建一个类实现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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

操场


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

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

在这种情况下,使用try_pollreturnResult<(), String>和 useResult::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(())
}
Run Code Online (Sandbox Code Playgroud)

操场

The Rust Programming Language 的第一版有这样的说法String作为错误类型:

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