TL;DR
Is there some better syntax to replace the verbose and annoying result.unwrap_or_else(|err|panic!("formatted string with error {err} and more infos"))?
If postfix is the solution, is it possible to define postfix macros only on struct/trait objects?
Currently I found it difficult to replace .unwrap() with .expect("Reason") since "Reason" usually contains valuables variable to be formatted.
When I ask how to write .expect with variables formatted, AI told me either using a verbose pattern, such as unwrap_or_else(|_|panic!(...)), or creating a &str with .expect(&format!(...)), which will facing the penalty that each time we called the function, a String will be formatted no matter whether the result is Ok or Err.
It seems that, a postfix macro might helpful, since we could write something like
foo.expect!("Reason")
which could format all the data we need.
I have searched for several posts of postfix macros, most of the worries are talking about "what to expand".
If we limit the postfix macros to trait/struct only, then what to expand is obvious.
And for macros that will expand a lot of things, perhaps a postfix will confuse users who read it.
Postfix macros that operate on a specific type are much harder than those who work generically, since they require access to type information, something macros currently cannot access, and letting them access will require a major re-architecture of the compiler.
I realize that postfix is often more convenient, but you could always write a normal macro:
expect!(foo.unwrap(), "didn't work :( (bar={bar})");
which at least to me actually reads better than the method expect:
expect(what_i_expect_to_happen, didnt_happen)
rather than
what_i_expect_to_happen.expect(didnt_happen)
which raises the common question as to how exactly didnt_happen should be phrased to make the most sense.
We do also have a way to represent a lazily formatted string: format_args! and Arguments. If expect could be generalized from taking &str to &dyn Display (or &impl Display) or some more specific trait, that would take care of most of the use cases of the unwrap_or_else(|| panic!(...)) pattern. That said, it's probably not exactly a priority to make it more convenient to panic on errors.
The only issue I have with let-else is that I generally do want the contents of the Err for the panic string, which leads me to have to fall-back to match:
let val = match foo {
Ok(val) => val,
Err(err) => panic!("error: {err:?}"),
};
But it would need someone to analyze the frequency and prevalence at which that would both be applicable and not just be .unwrap_or_else(|e| panic!("error: {e:?}")).
If you want to write macros, things will be harder:
unwrap!(//what to unwrap?
some_thing
.become_another_thing()
.and_do_some_work()
.finally_become_a_result()
, panic!("error occurs") //which macro accepts this panic? Am I put the panic! into the wrong place?
).in_case_you_want_to_do_more_things()
There are various ways to write equivlent code, but I’m trying to find a simple one.
My immediate idea is to employ a suitable wrapper function or method such as would be required to invoke a lambda. This treatment could be generalized by a trait.
If macro aliases were a thing and expect accepted impl Display, user may have aliased format_args! to fa! and used like .expect(fa!(...)). Of course such alias won't make it to the std
This makes me think that the correct solution here is to add a new method that's a hybrid of map_err and into_ok:
let value = foo.into_ok_or(|e| panic!("{e}"));
Basically unwrap_or_else but where the closure's return type is Into<!> rather than T. I don't tihnk this does anything that unwrap_or_else can't do, but it's more readable.
This already works today. I can't think of a realistic expression that returns Into<!> instead of !. So I'm not sure where the "it's more readable" comes from.
let value = res.unwrap_or_else(|e| panic!("{e}"));