123
The Compiler Is Your Best Friend, Stop Lying to It - Daniel Beskin's Blog
(blog.daniel-beskin.com)
Welcome to the main community in programming.dev! Feel free to post anything relating to programming here!
Cross posting is strongly encouraged in the instance. If you feel your post or another person's post makes sense in another community cross post into it.
Hope you enjoy the instance!
Rules
Follow the wormhole through a path of communities !webdev@programming.dev
Combined return types is a Rust feature.
I recently started learning Rust, and would prefer exceptions. Even Java checked exceptions aren't too bad to work with. Rust approach is Ok though and ir has a purpose.
You can use the
anyhowcrate for something quite similar to exceptions: https://crates.io/crates/anyhowIn terms of how it actually fits into Rust, you want to use
anyhowfor application code. If you're writing a library, thenanyhowis a no-go, because users cannot match on different error classes with it. (Much like you would want custom exception classes, so that users can catch them in different catch branches and handle them differently.)Every so often, you will also need matchable error classes within your application code, then you would have to replace
anyhowwith a custom error type there, too, of course.I will also say, IMHO it is fine to use
anyhoweven while you're learning Rust. It is so easy that you kind of skip learning error handling, which you will need to learn at some point, but you can still do that when you actually need it.In Rust you’re kind of stuck with it, but at the end of the day combined return types are just syntactic sugar for something a lot of languages can do. Even in plain old C there’s a pattern where you pass pointers to your return and/or error variables. In many languages you can return structs or similar. In some I’d argue it looks nicer than having to write Result<>, e.g. in Python or in Swift you can just return a tuple by putting things in parentheses. (Of course you can also still use something more explicit too. But if every function returned (result, error) by default and every call was like result, error = fn(), I don’t think it’d be necessary.)
However I don’t really know of any language where people prefer to use this over exceptions if exceptions are available. Even in C some people used to use setjmp/longjmp in macros to implement exceptions. Exceptions have their problems but people seem to overwhelmingly be in favor of them.
Personally I like exceptions in languages that have some kind of built-in “finally” for functions. For example defer in Swift. You can have proper error handling for a lot less typing in many cases because passing through exceptions is fine if your defer blocks handle the cleanup. And if you do want to handle an exception, Swift also has optionals, and a try? that transparently converts a return value into an optional that’s nil when an exception was thrown, and a coalescing operator ??, which means you can catch exceptions and provide a default value on one line, instead of a 4-5 line try..catch/except block or an error checking conditional for every call.
Interesting that you brought up
finally. I was learning Rust the last two days and didn't realise it was missing. There may be some other way of handling it.In many cases, you don't need an equivalent to
finally, because the cleanup is automatically handled via theDroptrait, which runs cleanup code when the object is freed from memory (similar to "destructors" in some other languages).Because of Rust's whole ownership thingamabob, it's generally entirely deterministic when this code will run (at the closing brace for the scope in which the object is defined, unless you pass that object outside of that scope).
In other cases, you don't need a
finally, because nothing forces you to bubble up errors instantly. You can make a call which fails, store the error in a variable, run your cleanup steps and then return the error at the end of you function.Sometimes, however, you do want to bubble up errors right away (via
?or earlyreturn), typically so you can group them together and handle them all the same.In that case, you can run the cleanup code in the calling function. If you don't to want to make it the responsibility of the caller, then pull out a small function within your function, so that you become the caller of that small function and can do the cleanup steps at the end of your function, while you do the bubbling within the smaller function.
There's also ways to make that less invasive via closures (which also serve as a boundary to which you can bubble errors), but those are somewhat complex in Rust, due to the whole ownership thingamabob.
I will say, I do sometimes feel like Rust could use a better way to handle doing something before the error bubbles up. But generally speaking, I don't feel like a
finallyis missing.scopeguard would be one way to get defer in Rust