RFC : Try-Catch Blocks

Hello Rust community!

I've created a RFC paper on how to implement Try-Catch blocks in rust without exceptions.

Before I submit a pull-request, I'd like some feedback on the idea.

What do you guys think?

Edit

So after a debate, it appears there is already an open RFC on the subject here: https://github.com/rust-lang/rfcs/pull/243, work is in progress. Also such feature is not likely to appear soon, as it would involve breaking changes.

1 Like

The motivation section doesn't show any examples of try! being unusable. Consequently we can't assess the improvement.

2 Likes

This article explains the problem:

As you can see, the try! macro makes things quite straightforward. There is however quite a big problem with it, and that is that all the errors from the function have to be the same. In many cases that is just wrong. A good example is my redis-rs library. Since all operations are happening over sockets you can either get an IOError or you might be presented with a redis specific error.

Maybe I should mention it

That article is rather old and could be outdated. The RFC has to stand on its own. The latest comprehensive work on error handling seems to be dealing with different error types well: Error Handling in Rust - Andrew Gallant's Blog

2 Likes

That article is about a year old and most likely outdated. The definition of try! shows that it expands to this (with more explicit paths):

match may_be_err { //Result<T, E>
    Ok(val) => val,
    Err(err) => return Err(From::from(err))
} 

This will perform a conversion from the returned error type E to something that implements From<E>. This means that it doesn't have to be the same type, as long as it can be converted to the return type of the function.

An other note: try and catch are not reserved keywords, according to this. That's a quite serious problem, because reserving them retroactively would be breaking. It would render the try! macro invalid, as well.

2 Likes

This has to be mentioned in the drawback section indeed.

My main questions are:

  • Is it technically doable?
  • Does this paper propose a better approach in Rust error handling than currently?

You might want to look at this: https://github.com/rust-lang/rfcs/pull/243. It's old, but it has a try/catch proposal and the comments may be interesting.

Generally speaking, a proposal that will require two new keywords, where one is the name of a very common macro, will most likely have a low chance of being accepted. As for if it's possible at all; maybe in a different form. Maybe as a catch macro... hmm...

2 Likes

After some thinking and experimenting, I would say that a try/catch macro would only add a slight improvement over what we have today.

The biggest problem is that I can't think of a good way to have error types that are more complex than plain identifiers, without both naming the type and an identifier (i.e. A(err_a): A<'r, T> => ...). Even with this ability, the improvement would still be small, I think.

An other problem is that try! is associated with early return and this macro doesn't really stand out as being a function of any sort, so it may even be more confusing. I guess one could learn to reason about it, just the way one learn to think that try! may return, but it's still something to consider.

Result, combined with try!, is a quite powerful tool. It was at least fun to play with the thought.

thank you @ogeon !
Couldn't understand in detail your macro so far, but sounds like the problem is more complicated than it looks.
I'm going to play with it.

I've read the beginning, and then went to the end, to learn about the status. Haven't precisely looked at the details, my understanding is that this approach is based on traits. However I could observe the team was very excited by the proposal.

Providing a working macro based solution would be a great start for this paper. If they can get 80% of the work done from a well designed macro, it wouldn't be hard to add keywords out of it.

You can read about macros here. They are quite cool and can do a lot of things, but not always exactly what you want. They are exceptionally bad at counting.

Go ahead and do whatever you want with it. :smile:

As I said, reserving new keywords is something that has a very low chance of being accepted before Rust 2.0 and I doubt that a try/catch statement would be reason enough. It's easier to make a macro like this and try to get it accepted, even if that would be hard enough given the general attitude towards exceptions.

I've been playing with macro in C, however here I feel like they are a lot more powerful :smile:

If we provide a straightforward/working solution, I can't believe they wouldn't accept it!

This feature to me, besides stabilizing the API, is the last serious problem to overcome! I just don't want to start a project, checking for errors at each single function call.

To me the problem is simple, I can live without an IDE, without autocomplete, but if I don't get try-catch blocks, I won't want to code at all in Rust.

They are not even close. C macros are essentially copy-pasting source code before it is completely parsed, while Rust macros operates on the abstract syntax tree after the code is parsed. That, in itself, prevents a few pitfalls, but they are also hygienic, which mean that they cannot cause naming conflicts and such.

macro_rules! assign_to_a {
    //this won't work, because this `a` is not the same as any other `a`
    ($value:expr) => { a = $value; }
}
macro_rules! assign_to {
    //this will work because we are taking the $a from outside the macro
    ($a: ident = $value:expr) => { $a = $value; }
}

Very well. You seem quite convinced. I would still recommend that you give the current system a chance and get more familiar with it before proposing a change.

[quote="raizam, post:11, topic:2663"]
I just don't want to start a project, checking for errors at each single function call.
[/quote]Your input will carry a lot more weight once you actually start a project and then relate your experience and point out specific problems.

3 Likes

I saw the current error system, and I can't be satisfied with it.
I Would recommend that you help me write this paper, and spend some of your energy to make something happen (with a working macro) lol

It's not my input anymore, it's also @ogeon's, and yours too now.
guys, I'll be waiting for pull-requests lol

I'm pretty sure that magically checking for and acting on expression with a Result type is not something the Rust compiler will ever do. [1] If you want to focus your energy on an RFC (which is a laudable thing to do) you should focus on either making try! better, or finding an explicit syntax solution that is compatible with today's Rust.

But you'll also want to look at the flip side of the coin. I found that having to deal with Results I often found better solutions for handling the exceptional case than just letting an exception propagate up the stack. One of them is map_or, unwrap_or and similar, which allows a much more compact handling of "default" actions than try-catch blocks would.

[1] After all, the Result type is not only used for error conditions.

3 Likes

I'm sorry to disappoint you, but I'm not convinced. The macro was a fun thought experiment and the only benefit it adds is a substitute to repeating error conversion and a more simple way to produce some default results. That's also the only benefit I can see with it. It will, on the other hand, prevent early returns, since it's essentially a function inside the function, and it can make it harder to react the way you want to particular errors.

Making a full blown try/catch expression, where you don't have to use try! inside it, requires knowledge about the return types of expressions and that is not something that macros or compiler plugins have access to. The syntax tree carries no such information.

I agree with @birkenfeld regarding try/catch in general. Macros are enough magic for me, thank you. I'm not convinced enough to help you write this RFC.

So far no one has openly expressed the wish to have try/catch blocks in Rust.
So far I'm on my own on this one.

Compiler plugins! I'm so happy to discover this!
Thanks!

I probably do agree with you somehow, but when an error occurs in the real world, there are usually only 3 ways to handle it:

  1. Create the conditions to make the error less likely to occur again (with additional checks and enforce Design by contract philosophy)
  2. Wait a certain amount of time, and try again (happens at high level only, mostly with network errors).
  3. Show a nice error message, log the error.If possible cancel the operation rather than the entire application, and keep things running as if nothing ever happened. At this point, a recovery strategy has to be chosen.

So here we have 3 use cases, but only 2 possible contexts:

  • The application is being developed, use-case #1 is the one happening during coding/tests cycles. Here, errors are addressed by fixing the algorithm itself and nothing happens in the error handler.
    a panic! message is just what is needed, with a detailed error.

  • The application is in production and an error occurs, the only solutions available is to choose between 2 and 3.
    Implementing these two behaviors requires only 2 functions for the entire app. So the question is:

why would you want to specify for every Result returned what to do when an error occurs, while you have only 2 options available?

This is why Exceptions are needed, it provides a centralized places somewhere, in the stack, so you can decide how you want your application to resume (or not).

Exceptions do tend to bring memory leaks, and unsafe de-allocation of resources, especially in native languages. But my proposal here is only faking exceptions, while conforming with Rust's safety requirements.

[quote="raizam, post:18, topic:2663"]
why would you want to specify for every Result returned what to do when an error occurs
[/quote]Why indeed if you can use the try! macro.

Let's assume that I'm a good rustarian and I decide to choose the try! macro. Here is how my code may look like (pseudo code):

 let my_file = try!(open_file());
 let my_text = try!(my_file.read_file());
 try!(my_file.write_file())
 try!(my_file.close_file())

well... should I try chaining these, see if it works? :smile:

Anyway, I couldn't convince anyone. I lost my day. I give up on convincing. I should rather start investigating on my own on the technical side. Or switch to D. difficult choice.
well I should at least try to resolve this by my own.