How do I idiomatically unwrap Rust errors with functions rather than closures?

Viewed 541

I'm struggling with error handling cleanly in Rust. Say I have a function that is propogating multiple error types with Box<dyn Error>. To unwrap and handle the error, I'm doing the following:

fn main() {
    let json =
        get_json_response(format!("{}{}", BASE_URL, LOGIN_URL).as_str()).unwrap_or_else(|e| {
            eprintln!("Error: failed to get: {}", e);
            std::process::exit(1);
        });
}

fn get_json_response(url: &str) -> Result<Value, Box<dyn Error>> {
    let resp = ureq::get(url)
        .set("Authorization", format!("Bearer {}", API_TOKEN).as_str())
        .call()?
        .into_json()?;
    Ok(resp)
}

This works well. However, if I have multiple calls to get_json_response(), it gets messy to include that same closure over and over.

My solutions is to change it to:

use serde_json::Value;
use std::error::Error;
use ureq;

fn main() {
    let json =
        get_json_response(format!("{}{}", BASE_URL, LOGIN_URL).as_str()).unwrap_or_else(fail);
}

fn fail(err: Box<dyn Error>) -> ! {
    eprintln!("Error: failed to get: {}", err);
    std::process::exit(1);
}

This doesn't work because unwrap_or_else() expects a Value to be returned rather than nothing !. I can cheat and change the return value of fail() to -> Value and add Value::Null after the exit(1). It works but feels wrong (and complains).

I could also do unwrap_or_else(|e| fail(e)) which isn't terrible.

Is there an idiomatic way to handle this?

1 Answers

As pointed out by @kmdreko, your code fails to compile because, while ! can be coerced to any T, fn() -> ! cannot be coerced to fn() -> T.

To work around the above, you can declare fail() to return Value, and actually return the "value" of std::process::exit(1). Omitting the semicolon coerces the ! to Value, and you don't have to cheat with a Value::Null:

fn main() {
    let _json = get_json_response("...").unwrap_or_else(fail).as_str();
}

fn fail(err: Box<dyn Error>) -> Value {
    eprintln!("Error: failed to get: {}", err);
    std::process::exit(1)
}

Playground

Related