I disagree with most of your arguments. I think that exceptions are obviously part of the API - they just tend to be a lot more hidden and less thoughtfully designed than the standard interface.
Wrapping or mapping internal exceptions to a API level library-specific type isn't lying, it's just basic data modelling - the same as mapping e.g., a database response to a HTTP response, including database errors to client-appropriate HTTP error codes
And error handling can and should frequently be managed at the call site into a library, even for exceptional cases. Consider transient errors like network failures - it's standard to handle and retry at the call site with a delay rather than immediately propagate to the caller.
All of this is made simpler and more explicit with the use of Result types or similar over Exceptions.
Fundamentally, I think Exceptions don't simplify anything, they just hide complexity - and Result types don't complicate code, they just describe the otherwise hidden complexity.
I disagree because the complexity doesn't exist. If you concentrate only on the places you can handle errors/exceptions and what you can handle that is significantly simpler than fussing over the individual error return types of every single function call everywhere. That's just a make-work project. The big advantage of exceptions is that you can mostly ignore all that noise.
The fact that some function somewhere can trigger a network exception has nothing to do with where I can handle it and do the retry. Even network failures are rarely handled at the specific exact function that calls the operating system to put a byte on the wire. It's probably at least 5 levels up before it even gets to my code.
Exceptions and properly using Result types are effectively equivalent. Add checked exceptions and they are almost exactly equivalent. But that misses forest for the trees.
> fussing over the individual error return types of every single function call everywhere. That's just a make-work project
Yep, it's a lot of work. But if you don't do that work of defining a behavior for every error, you create undefined behavior.
Undefined behavior is acceptable in some places (e.g., desktop apps; small web services); it isn't acceptable in other places (e.g., big web services). At scale, you have to know what happens for all inputs. In my experience, this is easier to do if you figure out the error hierarchy ahead of time.
I am the original article author. I was writing after authoring a new RabbitMQ client. Existing clients were throwing exceptions in the wrong places, leading to website stalls. Stalls happened in every client library I could find. The trick is that the RabbitMQ protocol behaves like an actor system: error messages aren't always tied to requests. It took careful error design to map server messages to exceptions, and that error design had to happen at the very start of the project.
Abstracting a bit from that experience:
* exceptions are "error + goto". The goto saves work 99% of the time; the other 1%, it's awful.
I believe that there are exceptions to every rule :). I'm not going to argue with your experience with RabbitMQ and your solution is probably totally correct. But I think making that a general rule is a mistake. I write all kinds of software from web applications, libraries, desktop applications, and even embedded software and there is no one single right way to do error handling. However, 99% of the time, think exceptions with a very small number of catch blocks is easiest, cleanest, and most robust way to go.
I don't believe exceptions create undefined behavior -- the behavior is very much defined. I know exactly how all my apps will react to exceptions I've seen and not seen. I also know exactly where those exceptions will be handled. I'm also very comfortable knowing that there is no error situation that will be not caught and dealt with in some way. I'd like to know why you think differently?
I think the exceptions are a goto has already been debunked enough. Whether you use exceptions or not, your errors will be handled in the same place in the code. It's just one place you're wasting effort manually propagating that error to that same place and in the other you are not.
Wrapping or mapping internal exceptions to a API level library-specific type isn't lying, it's just basic data modelling - the same as mapping e.g., a database response to a HTTP response, including database errors to client-appropriate HTTP error codes
And error handling can and should frequently be managed at the call site into a library, even for exceptional cases. Consider transient errors like network failures - it's standard to handle and retry at the call site with a delay rather than immediately propagate to the caller.
All of this is made simpler and more explicit with the use of Result types or similar over Exceptions.
Fundamentally, I think Exceptions don't simplify anything, they just hide complexity - and Result types don't complicate code, they just describe the otherwise hidden complexity.