I made a crate called `evil`, which lets you use the `?` operator as a shorthand for `.unwrap()`

This crate lets you write this, with each ? equivalent to an .unwrap():

#[test]
fn user_theme_preference() -> evil::Result<()> {
    let response = make_api_call("/user/profile/settings")?;
    let json: Value = serde_json::from_str(&response)?;

    let theme = json
        .get("data")?
        .get("attributes")?
        .get("preferences")?
        .get("theme")?
        .as_str()?;

    assert_eq!(theme, "dark");
    evil::Ok(())
}

Instead of this:

#[test]
fn user_theme_preference() {
    let response = make_api_call("/user/profile/settings").unwrap();
    let json: Value = serde_json::from_str(&response).unwrap();

    let theme = json
        .get("data")
        .unwrap()
        .get("attributes")
        .unwrap()
        .get("preferences")
        .unwrap()
        .get("theme")
        .unwrap()
        .as_str()
        .unwrap();

    assert_eq!(theme, "dark");
}

GitHub:

3 Likes

You might want to have a look at anyhow with feature backtrace enabled and pass env RUST_BACKTRACE=1 when run.

1 Like

I prefer eyre crate over anyhow, since I don't need to set RUST_BACKTRACE=1 and it shows file/line/column info automatically.

I compare evil::Result<()> with eyre::Result<()> in the README. In short, the former works on Options and the latter doesn't

This definitely has a valid use case for tests. Usually we use lots of unwrap or expect in tests (including doc tests), which is different than writing library or application code, where unwrap/expect are a deliberate choice for unexpected situations that should never happen.

So the current situation in tests with many unwraps is indeed a distraction, for example it prevents grepping all unwrap calls to have a small list of where unwrap is used in library/application code.

And the name evil for this crate is nicely chosen.

But I'm not sure whether I will use this in my crates before Try stabilizes, because I like to run tests also on the stable tool chain. Maybe if we could find a way to have a (degraded) stable equivalent?

I think the stable equivalent would have to be a proc macro that actually, literally translates ? into unwrap. It might not be as bulletproof though (e.g. it can't enter into closures, just in case; whereas inference can automatically decide when to affect a closure or not).

I have actually moved away from using unwrap() or expect() in tests. Applying Rust error handling in tests makes them more maintainable and also ensures that, in the event of errors in tests, it becomes clearer where something has gone wrong in the source code. The most important thing is that it works.

1 Like

I assume you use backtrace-collecting error types for this? Because otherwise you lose source code locations.

1 Like