marcantonio
marcantonio

Reputation: 1069

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

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?

Upvotes: 3

Views: 1823

Answers (2)

Dmitry Mottl
Dmitry Mottl

Reputation: 862

I'm not sure if that is idiomatic to Rust, but you can put all the stuff inside a closure and handle the Result afterwards:

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

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)
    })().or_else(|err: Box<dyn Error>| {
        eprintln!("Error in get_json_response(): failed to get: {}", err);
        std::process::exit(1);
        // or just return Err(err) if you don't want to exit
    })    
}

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

Upvotes: 0

user4815162342
user4815162342

Reputation: 155216

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

Upvotes: 3

Related Questions