I worry a little bit about writing here, after being chastised for „monopolizing“ the discussion, but I guess throwing an idea in still counts constructive, and that an idea with actual code is worth one more comment. I apologize if this was brought up before somewhere, but I’m not aware of it (if so, please share a link and I’ll cross out the post so it doesn’t distract people).
I like the idea of the attribute and doing it in a library. And I wonder if this could be stretched even further. So, as a mental experiment, could we implement the try
block in this way, as a macro? I know the try
block is in a merged RFC, has a keyword reserved, but it hasn’t been stabilized yet, so there’s at least theoretical chance to still play with it. Naming in the examples is explicitly stupid to not suggest anything (eg. bikeshed later, after deciding if this could actually fly).
I don’t think I can do exactly the try block semantics, but I can do a block that stops all early returns:
// Yes, only a lambda that is called right away, nothing fancy
macro_rules! stop {
($( $body: tt )*) => {
(|| { $( $body )* })()
}
}
let x = stop! {
let a = 2;
let b = 2;
if a > 0 {
return a + b;
}
panic!();
};
assert_eq!(x, 4);
This one does not ok-wrap the final statement (yet, see below). This on one hand makes it more verbose (and needs the Ok(())
), on the other hand, makes it work for non-error-handling situations too (like, if I have something like nom 3’s IResult, which has 3 variants, or iterating through something and early-returning first matching value and providing fallback after the for). But it does support the use case of trying to run a series of operations and getting a result of the whole, which is AFAIK the main motivation of try
block:
let result: Result<(), Error> = stop! {
foo()?;
bar()?;
baz()?;
Ok(())
};
Now, because some people do want ok-wrapping, let’s implement that too:
#![feature(try_trait)]
// If we want it to run on stable, we can use Ok
// there instead of from_ok, at the cost of flexibility.
macro_rules! wrap {
($( $body: tt )*) => {
(|| { ::std::ops::Try::from_ok({ $( $body )* })})()
}
}
fn do_stuff() -> Result<(), ()> {
Err(())
}
let y: Result<(), ()> = wrap! {
do_stuff()?;
// See? No O(()) here.
};
assert!(y.is_err());
Pros & cons
- (+) It is very minimal and low-cost. It can be put into a crate. It can be used right now (or, after someone comes up with reasonable names and puts it onto crates.io), without an RFC process, support in the compiler, etc.
- (+) It is more flexible by supporting other use cases than just error handling.
- (+) Due to the low cost, both wrapping and non-wrapping variants can be supported.
- (-) The macro syntax is less visually pleasing.
- (+) It would allow experimenting with it first and then promoting one (or both) of them to a keyword, if it is deemed it is used enough. Furthermore, there was a contentious discussion if ok-wrapping is a good thing or not. The decision to make it wrapping was made, but on predictions. This experimentation could provide some factual numbers, which are generally a better indicator. On the other hand, it is in a sense a solution by postponing the problem and could lead to re-opening old discussions.
- (?) The biggest difference is that
return
from within atry
block returns from the function, while this returns from the block. It’s not possible to exit the outer function from within thewrap!
macro. One one hand, this is a limitation. On the other hand, it makes the need to havethrow/fail/pass
control flow keywords go away (if I understand it right that breaking out oftry
is the main motivation for these) ‒ the core language already supports everything needed to make it useful. And I personally believe it would make thinking about flow control in the function easier, that thetry
's property of being able to return to two different places is in the general direction ofgoto
. I don’t know how often one would want to exit the outer function from within the block.
Questions
- While related to error handling, like the original proposal, and it got inspiration here, I don’t really want to hijack the thread if it would be considered inappropriate. Should I move it to a separate thread?
- Are there any further pros & cons I’ve missed?
- Is the difference in not being able to return from the outer function important?
- Does it sound like a possible step forward around error handling to anyone?
- If the answer to the above is yes, would it make sense for some of the major error handling crates (failure, error-chain) to adopt it? I can put up a crate myself, but if it got included in one of these, it could gain more popularity and the experiment would be more valuable.