try:
foo().bar().baz()
except Exception as message:
return message try:
foo().bar().baz()
except Exception as message:
return messageRust doesn't have exceptions, for many reasons. One is non-locality of exceptions. Even gotos but bounded by a function. Exceptions, however...those can take you anywhere.
But the other side, as you point out, is that exception many common patterns are much made more succient.
I've never understood this argument.
Where does `try!()` take you if there's an error? To the caller. If the caller also has a `try!()`, where does that take you? To the caller's caller.
Where does `throw new FooException()` take you? To the caller. If the caller doesn't have a `catch(FooException)`, where does that take you? To the caller's caller.
Edit: Note to commenters: I'm not arguing about explicit vs implicit returns. I do agree that explicit returns are nicer to read and reduce the surface area of a language. My comment is solely for the "Exceptions can take you anywhere" sentiment.
This becomes important because now your code no longer has invisible escape hatches 20 levels deep. You design your function with specific points where it may stop executing. You control when your function returns.
You can also store the error value and handle it later (Servo makes use of this pattern often -- e.g. a network request with a body of Err, which needs to be handled when we try to read the body).
> Where does `try!()` take you if there's an error? To the caller. If the caller also has a `try!()`, where does that take you? To the caller's caller.
You can say this about return, too. returns and try both take you to the caller. The caller then bubbles you up, because it explicitly returns or tries.
Yes, even the JDK is to blame for misusing checked exceptions.
The return value is a Result, which is an enum and must be handled in a match or other construct, or explicitly ignored with an .ok or .expect (that may be for Option only) which result in a panic if not successful.
The `try!()` in this case is analogous to checked exceptions. It makes the bubbling explicit.
The trick is "Exceptions" are more than just "Checked Exceptions"
The Rust equivalent would be something like:
`Result<_, Box<Error>>`
The thing is that exceptions do not require the manual CPS transform required to handle Result objects, which results in nicer code.
Even with syntactic sugar like do notation, the syntax is still different from 'normal' code.
Also the escape hatch to unchecked exceptions is convenient too (then again Rust has some form of it already).
Raising an exception does not. It's akin to a `return` and `goto` all in one.
---
I don't mean to criticize Python's choice.
I think exceptions are a good decision for a high-level, dynamically typed, scripting language.
I do not think exceptions are a good decision for statically typed systems language.
some_vec.iter().filter_map(|i|
i.to_something()
.map_err(|e|None)
.map(|i|Some(i)))
.collect()
I think that will compile. The `to_something()` might return an error, the important thing is that there is no exception and allows for clean error handling. In this case we're just ignoring errors. With exception handling, while it's not impossible to do the exact same thing, I believe this is cleaner (and yes, there are other ways to write this). try_block
some_vec.iter().filter_map(|i|
i.to_something()
.map_err(|e|None)
.map(|i|Some(i)))
.collect()
catch_exceptionAgain, I didn't say it was not possible in the other languages, I only stated that I didn't think it ends up being as clean (specifically in regards to C++, Java and C#)
On the other hand without non-local error handling it is harder to bail out of the outer map on a to_something error, if that's what's desired.
Either the full expression is successful or not.
You don't see the point, and that's fine. Initially when Java came out with Streams I hadn't worked a lot with ETL like functions. Then while with Rust I really got it, so now I want to apply that practice to code in Java, because it simplifies many areas of code. One thing that became very clear, is that Exception handling throws a minor monkey wrench into keeping the code simple. This is why I picked the example I showed.
You don't think it's cleaner, or makes a big difference, and that's fine. I personally find it to be easier to read and create more understandable code. You can have your exceptions, but after Rust I am totally sold on the Result for errors pardigm.
For example when sending a data packet over the network and it fails, I don't care if the error was in the communication, buffer, socket level or network layer, just that the packet could not be sent.
That is what we need to retry, sending a new packet, not the lower level operations.
Languages with exceptions, which I use since 1993, allow me to choose using an exception, transform it into a plain error code or monadic error. I can have it all and choose which flavour I want to use, depending on the use case.
But as you say, it is a matter of style.
This is a huge problem with wrappers in general that signature of wrappee becomes part of wrapper signature. Since exceptions are part of function signature they make the problem worse. And since most if not all languages do not enforce exceptions in signature there is literally no way to know what will be thrown if runtime polymorphism is used. Which results in situations where querying REST endpoint written in a language we all love to hate returns trace in the body, ooops :)
There will be exceptions I don't know about, and that's fine. I let go the desire to fully know the set of all exceptions that can be thrown. Instead, I weaken my assumptions and code as if everything could break, and that makes code quite robust. For example, I try to arrange the code so that it is transactional, which can be easy if the approach is functional. Otherwise, places that must be protected get a catch-all handler, etc.
Foo.Bar.Baz;
exception when Program_Error => Put ("WHoops");
when Constraint_Error => Put ("Dang");
when Gaim_OVer_Main => Put ("??? D: ");
when others => Put ("IDC...");foo().bar().baz()
ie, let someone higher up the stack handle the error.