shepmaster / snafu

Easily assign underlying errors into domain-specific errors while adding context
https://docs.rs/snafu/
Apache License 2.0
1.45k stars 61 forks source link

how to combine ensure! and whatever! #328

Closed hellow554 closed 2 years ago

hellow554 commented 2 years ago

There's this example in the docs:

if moon_is_rising() {
    whatever!(
        "We are recalibrating the dynamos for account {}, sorry",
        account_id,
    );
}

What I would like to write is:

ensure!(
    !moon_is_rising,
    whatever!(
        "We are recalibrating the dynamos for account {}, sorry",
        account_id,
    )
)

This is currently not possible. I want to avoid is to write an if statement and use ensure! consistently in my code.

Enet4 commented 2 years ago

In that case there are two possible ways right now. One is to use with_whatever_context on an Option<_> or Result<_, NoneError> representing the tested predicate.

pub fn get_bank_account_balance(account_id: &str) -> Result<u8> {
    Some(())
        .filter(|_| !moon_is_rising())
        .with_whatever_context(|| {
            format!(
                "We are recalibrating the dynamos for account {}, sorry",
                account_id
            )
        })?;

    Ok(100)
}

The other one is to pass a Result<_, NoneError> as the first parameter to whatever!.

pub fn get_bank_account_balance_2(account_id: &str) -> Result<u8> {
    whatever!(
        if moon_is_rising() { Err(snafu::NoneError) } else { Ok(()) },
        "We are recalibrating the dynamos for account {}, sorry",
        account_id
    );

    Ok(100)
}

Admittedly, both are a bit of a mouthful. I would say that it might be possible to extend ensure! to support the code below, as it's seemingly unambiguous from the existing match. Either this or introduce an ensure_whatever macro.

pub fn get_bank_account_balance(account_id: &str) -> Result<u8> {
    ensure!(!moon_is_rising(), "We are recalibrating the dynamos for account {}, sorry", account_id);

    Ok(100)
}
shepmaster commented 2 years ago

it might be possible to extend ensure!

Something like this might be the right direction:

macro_rules! ensure {
    ($c:expr, $s:literal $(,$v:expr)* $(,)?) => { println!(concat!("Literal: ", $s), $($v)*) };
    ($c:expr, $e:expr $(,)?) => { println!("Expression: {}", stringify!($e)) };
}

struct Ctx { a: i32 }

fn main() {
    ensure!(false, Ctx { a: 1 });
    ensure!(false, "one");
    ensure!(false, "two {}", 3);
}
8573 commented 2 years ago

I had the impression that the whatever! macro and "stringly-typed" errors (as the documentation calls them) were intended as a secondary feature — "a first step to better error handling" — with more structured or strongly-typed errors intended as the main thrust of SNAFU. Perhaps that impression was mistaken, but, if it was roughly accurate, wouldn't this be making stringly-typed errors a more central and more encouraged use, by integrating specific support for them into the main ensure! macro?

or introduce an ensure_whatever macro.

Respectfully to @shepmaster's judgement, I would support this alternative. I like that not overloading ensure! as much as the other proposal both makes the code clearer (in my opinion in this specific case, and fitting the Rust decision against general function-overloading) and doesn't blur the distinction between the stringly-typed and strongly-typed styles.

shepmaster commented 2 years ago

Feedback welcome on #330

shepmaster commented 2 years ago

Published in https://crates.io/crates/snafu/0.7.1