Related: Only zig has fast errors with traces. Other languages have no traces or a high performance cost. Look up error return traces in the docs.
Related: Only zig has fast errors with traces. Other languages have no traces or a high performance cost. Look up error return traces in the docs.
Don’t let all the negative comments get you down. It happens to every language author. (See some of the nasty comments on Elm, Clojure, Go, Jai, etc.) I, for one, am happy that all of these languages exist, even those that don’t scratch my itches.
I like Rust and other modern languages too, but Zig strikes me as just about the best possible contender for a true C/C++ replacement (due to seamless interop, and a very practical and well designed simple language core — as opposed to the complexity of something like Rust, for example).
But that's entirely fine! Programmers are far too obsessed with exactly which functions trigger which errors when it absolutely doesn't matter. All you need to know is what errors you can handle and where in the code you can handle them.
If there is a network exception and can recover and retry it at the start of the operation, it literally doesn't matter which of the thousands of functions up the call stack could have possibly triggered it. The only thing you need to know is the top-level network exception and where your network processing code starts. And if you can't handle a network error, it literally doesn't matter if one was triggered. The best you can do is abort and record a really good stack trace for that type of error.
Surely also what errors can occur. Can the network using library throw disk IO errors? Permissions errors? Maybe it handles all the network errors internally to the library and I don't need to deal with those at all.
You don't really need to know in the specific what kinds of errors can occur. If it's possible to recover from an exceptional situation, it's only useful to know if that situation is possible so you can avoid writing code you don't need to. But there wouldn't be any harm in writing an exception handler for an exception that can't happen except for that wasted effort.
The flip side of this is I shouldn't need to worry about exceptions that cannot be thrown. When you say all you need to know is what you can handle and where, that list must be a subset of the possible list of things that can be thrown. There's no point worrying about whether I should be retrying something due to network faults if it never uses the network to begin with.
For your example if retrying network, I prefer to simply have a "ShouldRetry" property/interface on the exception itself since the triggering code has the best knowledge of how it should be handled. No need to know every possible network exception and sort them into retry or not retry.
> Is it something I can let the user retry if they know what's happening (e.g. IO error because the output folder doesn't exist)?
My favorite error handling is when you can just put a single handler at the event-loop of a UI based project. On exception you just show them the message and continue running. The stack unwinding code ensures the application maintains correct state and that the operation is unwound. If the user clicked "save" and a failure occurred they get the message and can retry if they want.
I admit there is an elegance to exceptions, but when I'm trying to write reliable code, I find reasoning about exceptions significantly increases my cognitive load. Error handling is a place where a little more verbosity is okay because it keeps my focus on the local context rather than having to consider the entire call stack. I basically agree with the TL;DR of the article: "My point is that exceptions are too hard and I'm not smart enough to handle them".
[1] https://blogs.msdn.microsoft.com/oldnewthing/20050114-00/?p=...
I used to think "this is hard, I need to learn about more systems until I'm smart enough to do this."
Now I think "this is hard, there's probably something wrong with the architecture. I'll fix it."
The idea that error states are perfectly knowable everywhere in the code is a myth. Even if that were possible at one moment, the instant anyone changes code anywhere it will immediately be wrong.
This is only true if your application has no state or invariants that could possibly be invalidated in the face of exceptions. For instance, what would be your solution to the 'NotifyIcon' example given in the linked article?
> The idea that error states are perfectly knowable everywhere in the code is a myth. Even if that were possible at one moment, the instant anyone changes code anywhere it will immediately be wrong
This applies equally to both error codes and exceptions. If a method N layers down in your call stack changes its behavior, that's a potential breaking change regardless of your choice of error handling.
The notify icon code is poorly structured to begin with. Simply creating a NotifyIcon object adds it to the UI? That's an awful design. If there was an add to UI step then it would be a non-issue; the half-constructed NotifyIcon object would never get added to the UI. This issue is not magically resolved by having to explicitly handle every error; you can make the same mistake with twice as much code.
> This applies equally to both error codes and exceptions. If a method N layers down in your call stack changes its behavior, that's a potential breaking change regardless of your choice of error handling.
I'm not talking about changing behavior, I'm talking about changing implementation. Behavior is part of the contract. But being able to safely change implementation is the fundamental principle of abstraction and is the basis for polyphorphism. If a method today does a calculation using a database but tomorrow is refactored to use a webservice -- as long as the contract/behavior is unchanged -- then the rest of the code shouldn't have to know about it.
i.e. checked-exceptions?
I prefer a more decoupled/late-binding approach to error handling; so far Common Lisp does it the best I've seen.[0] The key insight CL people had was that often in the face of an error, you need help from somewhere away from your local context to figure out how to resolve it, but then for many cases, you want to return back to where the error has occurred and take the resolution path with your local context. In other languages that automatically unwind the stack to the exception handler, it's too late.
[0] http://www.nhplace.com/kent/Papers/Condition-Handling-2001.h...
Then it's "checked" but catching is optional.
F# is a language this is a lot like this, and I've recently been unable to get the editor with these features working in Linux, and it makes it rather horrible. If you had to remote in to a server and use vim, it could be rather horrible, and so on.
If we can get something like the language server idea working, really well, on all platforms and supported by all editors, then designing languages around certain IDE assumptions would more often be a good idea I guess.
It's a nice in-between something like a Smalltalk image-environment-IDE / Lisp Machine and a dumb IDE/editor that starts from source code and has to parse into AST into its own...
Out of curiosity, have you compared this to Rust’s backtrace mechanism? I’d be interested in the perf difference, if it’s available.
Looking at the source code https://docs.rs/crate/failure/0.1.3/source/src/backtrace/int..., "failure" uses https://docs.rs/backtrace/0.3.9/backtrace/struct.Backtrace.h... which is the callstack at a single point, not a trace of how an error was propagated.
That's the whole point of exceptions.
Yes, and that's a very good thing.
> Many c++ projects disable exceptions entirely.
Yes, and they're objectively wrong.
> Writing "exception safe" code is tricky and non-obvious.
The word 'exception' here is redundant.
Exceptions make it much harder.