
C++ Error Handling: Why Use Eithers in Favor of Exceptions and Error-Codes - fla
https://hackernoon.com/error-handling-in-c-or-why-you-should-use-eithers-in-favor-of-exceptions-and-error-codes-f0640912eb45
======
workincog
And of course his "simple" example of an `Either` class leaks and doesn't call
destructors, because C++ unions do not cleanly support types with nontrivial
destructors.

Meanwhile the open source project he links to has an implementation that is
hundreds of lines of template magic that I gave up on interpreting.

I've dealt with codebases like this and I strongly recommend against using
this kind of arcane template incantations in production. It becomes an
impenetrable fog for newbies, and a morass for veterans with something to
prove.

~~~
fnl
Can you please point out what about Buckaroo's (i.e., the author's) solution
is wrong? I take it, his version of Either is copied from the neither project
[1], so the destructor of the union is:

    
    
       ~Either() {
         if(isLeft) {
           leftValue.~L();
         } else {
           rightValue.~R();
         }
       }
    

Which does explicitly call the destructors. (Obviously, if you only were
referring to his eight-line toy version of Either for the blog, please forget
my comment.)

[EDIT: re-reading your comment once more, I see you did indeed mean the blog
implementation; please ignore this comment.]

[EDIT2: BTW, it seems the author is "affiliated" with that LoopPerfect/neither
implementation.]

[1]
[https://github.com/LoopPerfect/neither/blob/master/neither/i...](https://github.com/LoopPerfect/neither/blob/master/neither/include/either.hpp)

------
Suncho
The whole point of C++ exceptions is that they decouple the code that might
raise exceptions from the code that handles them.

If you know how to handle an error when it arises, then you shouldn't be using
exceptions. If you're wrapping a try block around every piece of code that
might throw an exception, then you're doing it wrong.

The technique described in this article is not an alternative to exceptions...
unless you were using exceptions wrong in the first place.

EDIT: Replaced "any code" with "every piece of code."

~~~
IshKebab
> If you're wrapping a try block around every piece of code that might throw
> an exception, then you're doing it wrong.

Well then the C++ standard people are doing it wrong, because something as
expected-to-fail as converting a string to a number throws an exception.

What am I supposed to do if I want to load a text file containing numbers and
print out where it failed? Yep, I have to wrap every string-to-number
conversion in a try-catch block.

Or in reality I make a wrapper function that does it. This is why exceptions
are stupid - 80% of the time you _want_ the context provided by the program
counter in order to handle the error.

int string_to_int(const std::string& s, int def) { try { size_t pos = 0; int v
= std::stoi(s, &pos); // Check that the number was parsed from the entire
string (stoi ignores trailing garbage). if (pos == s.size()) return v; } catch
(std::logic_error&) { // This occurs if there is no conversion (e.g. no
digits) or the number doesn't fit in an int. } return def; }

~~~
kazinator
> _Yep, I have to wrap every string-to-number conversion in a try-catch
> block._

Ah, that's simply because C++ exceptions are unwind-only, non-restartable.

"Blub" exceptions.

> _This is why exceptions are stupid - 80% of the time you want the context
> provided by the program counter._

Exactly.

Imagine if a CPU exception threw away that context. We couldn't implement
virtual memory, which requires faulting instructions to be precisely
restarted.

~~~
AstralStorm
Generally the context is held by most implementations (And accessible by
implementation specific tricks), but that is not required by the standard as
some platforms have highly limited stack Or other storage for such data.

Why would you want to restart same code after an exception anyway? Conditions
have changed...

------
fetbaffe
Once upon a time I hated exceptions, but today I like them more and more for
each day. The hate was something I inherited from the early languages I worked
with where exceptions where uncommon and was considered de facto wrong, it was
the C approach of error handling.

What I learned working on a medium-sized web project is that you have too
_few_ exception types. Yes, create more unique exceptions for each situation
and avoid reusing them across the project.

Too general exceptions make your code base suck hard. Complexity of exceptions
with small benefit because you can't discriminate between them.
_ObjectNotFoundException_ is pretty useless but _AccountNotFoundException_ is
much better. Don't be afraid of types, use it to your advantage.

Optimize the happy path and fail fast on the sad path.

Note, have not tried this approach on a C++ project yet, maybe it will not
work as well there.

~~~
AstralStorm
Works just as well until one of the types leaks accidentally by confusion.
This is why you should have your exception types derive from common ones so
that they can be reasonably caught by general handlers.

------
koja86
I am quite used to C++ exceptions and strongly favor them against error codes
mainly because of error handling decoupling and the fact that exception
(unlike return code) cannot be passively ignored - if you want to swallow it
you need to do it rather explicitly. If you forget to handle exception it
crashes loud and that I prefer.

If anything I would be very please if we got advanced exception catchers in
C++. Recently I drooled over Ada exception handling capabilities:
[https://en.wikibooks.org/wiki/Ada_Programming/Exceptions#Exc...](https://en.wikibooks.org/wiki/Ada_Programming/Exceptions#Exception_handlers)

------
stinos
Also worth mentioning: LLVM's ErrorOr [1] which is like Either but with the
right always an std::error_code. Rather convenient for APIs.

Also a remark: the author uses left()/right() calls excplicitly to construct
Either instances. This should not be needed (given Either has the proper
constructors). One could argue it is clearer, on the other hand one could say
it adds unnecessary visual noise. I'm obviously in the latter camp else I
wouldn't make this remark :]

[1]
[http://www.llvm.org/docs/doxygen/html/classllvm_1_1ErrorOr.h...](http://www.llvm.org/docs/doxygen/html/classllvm_1_1ErrorOr.html)

------
makecheck
I'm not sure I follow. Assuming that lambdas can indeed be optimized/inlined,
shouldn't the same performance gain exist with a single error-handling
std::function (lambda) parameter in an otherwise-normal function? In other
words, avoid wrapping the entire return value in a special type, avoid weird
calling conventions, and simply supply a block to invoke if/when there is an
error (and if there is no error, do nothing different).

~~~
slavik81
No, performance would probably be worse. For one, std::function is not just
for lambdas. It uses type-erasure and dynamic allocation to hold any sort of
callable object/function. It's a generic type with a complex implementation
and I wouldn't assume it would be optimized away.

Additionally, when using Either the lambdas are in the caller and are invoked
almost immediately by map. The scope is small and they do not enter into any
other compilation unit. If you pass them into the called function, the
compiler is only going to be able to optimize them if the called function can
be inlined. Even then, the optimizer will have to be smarter, because the
error handler will travel through a much larger scope and a type conversion.

~~~
AstralStorm
Either inserts a conditional and union dereference. Unions are pretty terrible
on their own to optimize, even with strict aliasing.

The pointer dereference and call overhead is not big and error handling code
is supposed to be cold anyway.

------
ensiferum
I feel that the "error" handling is one of those "core things" that people
have rarely fully understood. Especially newbies starting are often confused
and things get muddled. In all honesty it took me +10 years myself to reach
some kind of clarity on this. Anyway I find it that when you lack that clarity
your code will be quite messy (isn't this always the case?) and things will
get muddled. And since error handling is so precarious your implementation
quality will suffer considerably.

So below is my take on this with 3 clear labels and guidelines on how to
apply. Hope this helps.

There are 3 kinds of "errors".

1\. Bugs.

\- created by programmer.

\- invalid state of the application - >it has transcended it's own logical
realm and you can't reason about its behaviour anymore.

\- null pointers, OOB, etc.

2\. Errors that are "expected" part of the program execution.

\- you need to write logic flow to deal with this

\- it's expected and quite normal that this might happen

\- incorrect (user) input, file not found, socket timed out etc.

\- this is really just an error from the end user's perspective.

3\. Errors that are "unexpected".

\- some very unexpected error

\- system resource allocation failed, out of memory, out of file handles etc.

\- critical resource was not accessible (for example a "must have" config file
was not found)

How to deal with these?

1\. Abort and dump core. Yep seriously, just do it. Blow up with a bang and
leave a stack trace that you can analyze in the post-mortem debugger and see
what went wrong. As a result your application will be simpler and more
straightforward. Simply, don't try to write logic to deal with programmer
failures. It will just clutter your program, mask the problem and make fixing
it harder.

    
    
      int divide(int x, int y) {
        if (y == 0) {
          throw std::string("Divide by zero");
        }
        return x / y;
      }
    

passing y=0 the function is clearly a bug. It'd be much better to core dump
here. Unless the function was designed with double purpose of validating the
input and performing the actual function. I'd much rather split these into two
different functions, since the core function may be called from different
contexts, some of which might not require any input validation at all even.
And the validation function clearly should not be using exceptions either
since it's quite normal and expected that input from external sources (such as
the user) can be malformed and you probably want to write logic that then
bashes (sorry.. informs) the user about their wrong input.

2\. Use error codes.

3\. Use exceptions.

~~~
stinos
Hear, hear. I won't say this is _the_ best advice in _all_ circumstances but
we settled for roughly the same principles and in the end it just works out.

In case 1 we usually use asserts though (which are always on), but that's more
of a preference because it states intent (asserts are just used to guard
against insane programmer behaviour, and they are used judiciously, to the
point we will refuse large patches which do not introduce any asserts) and
makes debugging easy (since they have the debugger break). As a result, code
became less buggy. Programmers don't want to trigger asserts because they are
'crashes' and being the cause of one is usually the thing you don't want to
be. As such, people become more aware of every single line of code written,
every input provided, ... . Yeah there might be other ways to reach that
effect, but using assert all over the place did the job just fine.

~~~
ensiferum
Yep, I also have my own assert macro that when violated terminates the process
with a core dump unless running in a debugger when it triggers a break point.

Works out fantastic.

------
_pmf_
With exceptions, I can trivially pass error information through my whole call
stack without any manual work and boilerplate. The whole syntax clutter / OMG-
but-try-catch-is-so-ugly is a very stupid argument: with exceptions, you can
opt out of local error handling and opt-in at a higher level in the call stack
without losing any information. With return code / error codes, this is just
not possible. So exceptions declutter application code to a high degree.

Exceptions are to error codes as functions are to goto: there are valid cases
where you want to prefer the latter, but they are few and far between.

~~~
AstralStorm
It is possible with global or contextual error state.

This approach is used in iostream for example, and in a broken way in C
library via errno. (Which does not work unless you check it very near.)

Similar approach is often used in databases and file systems, marking an
object as dirty or broken.

Like the above, it is prone to ignoring errors and attempting to manipulate
such object.

------
fpoling
I have seen that using error singletones together with logging at the point of
error worked rather nicely. Surely as a global state it is not thread safe,
but when the state belongs to a component with well defined API and uses once
an error, always an error strategy so error recovery requires constructing a
new component, then thread-safety is trivial to address.

A bonus of this approach is that error path through code is the same as for
the non-error case. Thus getting good coverage for error cases in unit and
integration tests is easier.

------
MycroftJones
After a few years of using newLisp, I started using Eithers in my C and LISP
code. Didn't know you called them that. I guess the idea has been "in the air"
for a couple years now. Possibly people are being inspired by the multiple
return values offered by Go.

What surprised me about this link is that the author was writing C++, but
using very Haskellish language and terminology. He even made the C++ code look
somewhat like Haskell code. Wow.

------
gbersac
I used to do a little c++ as a student and never found a way to correctly
handle error using exceptions. I am now a scala user, and in scala Either is
the canonical way to handle errors. I understood the Either construct quickly
and after a year using it, I wouldn't come back to the c++ exception.

Note that in c++, the absence of algebraic data type made it a little bit hard
to understand how Either is coded.

------
Kaali
Monadic error handling is quite nice for domain specific errors, but for
exceptional situations which are not supposed to happen, you still need some
sort of an exception system. And even with domain error conditions, exceptions
has a nice property of saving a stack trace, which can make error hunting a
bit simpler.

~~~
dullgiulio
Exceptional situations as in crashes, like writing to 0x0 address?

Otherwise no, there is really no good reason for having some orthogonal value
returning system that can jump up the stack until it's caught (if ever.)

Many situations that are often considered exceptional are really not: cannot
connect to server, no such file or directory, cannot bind to port...

~~~
AstralStorm
Any situation that prevents functionality from working and should not happen
in normal flow is exceptional.

Including such connection failures or file open failures. They may have to be
handled, but not at cost to the hot path.

You cannot typically just "eat" such an error with default behaviour and
expect whatever relied on it to work properly.

------
shmerl
So it's monadic error handling in C++? Looks quite Rust like as well.

------
partycoder
Check out std::optional from C++17 (aka C++1z)

