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).
Indeed, and it is generally cleaner. The functional equivalent is continuation passing style. However, it just pushes the problem one level up, so is not a full solution.
It works well when combined with the some usual techniques like the error singletons and exceptions. You can also set an error code you need by capturing the result variable. Or just do what you need in the handler itself.
Returning a special type is about as annoying as returning and handling an error code.
Oh, there's many problems with suggested approach.
- Callback-oriented programming is its own deep can of worms. What do you want to inline where? In case of leftMap, it's easy for a compiler to inline flatMap, because it's small. What if you're providing a lambda to a huge function? What about variable captures? Really, we've been there with JavaScript.
- It will either incur a yuuge runtime overhead or will require a new calling convention.
- What if you want to transform your data multiple times? Will you use nested lambdas?
IMO, using sum types is the best solution to this problem. Sum types are not "weird special type", they don't require "weird calling convention", C++ is weird for NOT supporting them.
We have also been there in a variety of functional languages and no problems there.
What runtime overhead, a function call? Pointer dereference? Even template magic on the error monad type won't save you from that one. The problem is shifted to the dereference. There you get either to handle an exception or error code, or explicitly check the state of the object.
Nested lambdas do work reasonably well.
But remember, were generally talking about error handling, not a callback that is supposed to run big transforms.
std::function and lambdas are two very different things. std::function can store a lambda, but it is a memory-allocating library class, while a lambda is a language-level construct. Very important to understand the distinction between these two.
There are improved template magic versions of std::function that do not inhibit inlining for stateless lambdas passed directly thanks to additional magic.
At worst the overhead is a function call anyway and inhibition of inlining in the error path. Generally nothing to worry about.
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.
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.
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 :]
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."
> If you're wrapping a try block around any code that might throw an exception, then you're doing it wrong.
CS student here, can you elaborate on this? What code would you wrap in a try block if not code that could throw an exception? Are you suggesting try blocks should only be used to insulate function calls we know to be unsafe?
The point is to handle exceptions in appropriate locations, and pass exceptions up if you cant handle them. I think theyre saying that throwing a try block around an exception just to avoid dealing with it is a problem
Sorry. I said "any code," when I meant "every piece of code." Edited.
C++ programs normally throw an exception when a C program would call exit() or abort().
So I would start by having one try block that surrounds your main() function with a catch-all block that prints some sort of error message before your program terminates. This is often all you need.
If a situation arises in which you can handle an exception without terminating your program, you can throw a try block in there too.
Exceptions automatically provide for you what you would write by hand in languages like C:
// The C way
int error_flag = EVERYTHING_IS_OK;
int checked_div (int numerator, int denominator, int *quotient)
{
if (denominator == 0)
return 1;
*quotient = numerator / denominator;
return 0;
}
int foo (int a, int b, int c)
{
int q;
int err = checked_div (a, b, &q);
if (err) {
error_flag = FOO_DIV0;
}
return q+c;
}
*
// The exception way (notice that there's no try block)
int checked_div (int numerator, int denominator)
{
if (denominator == 0)
throw runtime_error ("Divide by zero!");
return quotient;
}
int foo (int a, int b, int c)
{
return checked_div (a, b) + c;
}
Clearly, if you don't use built-in exceptions, you have to manually simulate the exception mechanism. The most popular strategies for simulating exceptions involve some combination of systematically returning and checking error codes or systematically setting and checking global error flags. I've demonstrated both above. The linked article is an example of the first strategy.
Returning error codes prevents functions from directly returning their results. The "eithers" strategy is an attempt to make this a little smoother, but it doesn't solve the problem that it makes your code weird. Global error flags have their own host of problems. If you fail to check for errors, havoc will ensue. Furthermore, multithreaded environments turn global error flags into a tangled web of deceit.
Nevertheless, these ad hoc strategies often suffice because the unusual nature of exceptional errors can prevent error handling code from clogging up your primary code path too badly. But even for simple situations, the compiler can do better than you can. Why add unnecessary complexity to your code?
You should write try/catch blocks only when you know how to cope with exceptional errors. Needless to say, they should be pretty rare. But lets say that when a particular invocation of foo() fails, we want to use the maximum integer value as our result. Here's how it looks:
// The C way
int bar (int a, int b, int c)
{
int result = foo (a, b, c);
if (error_flag != EVERYTHING_IS_OK) {
assert (error_flag == FOO_DIV0);
result = numeric_limits<int>::max();
error_flag = EVERYTHING_IS_OK;
}
return result;
}
*
// The exception way
int bar (int a, int b, int c)
{
try {
return foo (a, b, c);
} catch (runtime_error)
return numeric_limits<int>::max();
}
}
Hmm... The exception version doesn't look that much simpler. After all, the assert() call will be compiled out of release builds. But C++ compilers can, and most modern ones do, generate code for the exception version as if it were written more like this:
int bar (int a, int b, int c)
{
return foo (a, b, c);
}
// This is only called when a runtime_error exception is thrown.
int bar_catch (int a, int b, int c)
{
// return to the same place bar() returns to
return numeric_limits<int>::max();
}
That's right. Even in the presence of a try/catch block, the code for foo is generated without the try...
Haven't checked CPython's extension API but Java's JNI does not allow exceptions to propagate through native code. When you call a Java method from JNI it will return and you have to check whether there is a pending exception, and do whatever is sensible in your native library. If a native library called from Java throws an exception that is not caught in that library then I think all bets are off - it probably depends on your language/platform ABI. The best case scenario would be that your Java stack gets nicely unwound in some way, but given it wouldn't be a Java exception object being thrown up the stack I'm really not sure what would happen.
Unwinding the stack through native library or language boundaries is fraught with potential problems and language interop APIs have to be designed carefully round that sort of thing.
I dislike exceptions, because they are magic. You can't tell what exceptions the piece of code you're calling will throw. It can also easily change with a library upgrade.
I like exceptions, because they are magic. While prototyping or for small utilities it's perfectly OK to "crash" on any error - nice error message can help then.
However I prefer when the final version handles all errors explicitly. I want to see it documented in code that error handling was not forgotten and it was deliberate to push certain errors back the stack. Of course it can work with documentation telling that this library never catches any errors, but it will put the burden on me. I also prefer to have a code, because documentation, more often then not, is lying.
Having written all that, I'm wondering how exceptions work with Python's "Explicit is better than implicit". I guess in case of exceptions this rule has lower priority than some other rule that priorities simple looking code. That would align with my use of Python - strictly for one off utils and prototypes.
I like them, too. You can easily opt out of the "checked-ness" by wrapping it in a non-checked exception, so the argument that it pollutes your application is just a non-issue for me.
I worked on huge old codebase. Mostly int return values were used as error indicator. But there were at least 3 non-compatible error enums (OK/ERR, PROCESSED/UNKNOWN/ERR, 1/0). Those enums partially used the same int values so you had to make sure to not mix them up (as you pointed documentation was frequently incorrect). Usually it took me several hours to check that error handling in some function is consistent or determine which of those enums is used. You can make huge mess with explicit error handling too.
> 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;
}
> 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.
I wouldn't say that they are doing it wrong. They throw an exception because they don't want to make assumptions about how you would want to handle the error.
What would you prefer the std::stoi() function to do?
Then std::atoi() would be making the assumption that you want to handle the error as soon as the function call returns.
Furthermore, if you don't check the "success" flag, then your function would fail silently. The exception ensures that something reasonable happens (program termination) when the client does nothing to handle the error.
exception shall occur in exceptional circumstances because they are costly in C++. A corrupted data file is a good case. exceptions in C++ shall not be used to handle and end_of_file.
This programming style is very specific to the language. In another language, exception may be used more widely.
> exception shall occur in exceptional circumstances because they are costly in C++
The upside is that the happy path (without exceptions) can be branch-free (or at least contain fewer branches than equivalent code handling errors), which is beneficial to performance (hot path stays hot).
Converting a string to a number throws an exception because either you are sure it's a number and an exception is an elegant way for your program to die in case there's a bug or an exceptional condition; or you aren't sure and you need to validate the input and deal with bad inputs on your own before attempting the conversion.
Because functional programming ideas were not as common when the C++ standard was defined. Back then, exceptions were all hip, and all the cool kids on the block played with them.
> 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.
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...
> 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;
}
(A Rust-like `std::variant` return would be better here but we don't use C++17 yet in this codebase.)
I only "partially agree": I often wrap entire sections of a program in a try block. This way, it can catch any unexpected errors, report them, and put the program back into a manageable state where it can keep running.
So I think exceptions are a good thing for unexpected outcomes; Resource errors, programming errors, etc. The problem is, we use them in cases where the error is, technically speaking, a possible, or "expect-able", outcome (such as the classical example of de-serializing data). In those cases, I fully agree, a type-level, explicit handler using the Either monad would be better.
I even think, the "expected case" is why Java introduced the notion of annotating exceptions. But as there are two different use-cases, this did not mix well...
Often, an error state machine is better than error code passing. (along the result or separately)
Such a design forces you to handle all errors near the place they occurred without the syntactic overhead. (Threading problems are overrated. Easy to dodge by providing a Context attribute which then works like IO monad in Haskell.)
It essentially works like exceptions without a forced stack unwind and return - which may or may not be good. Both are easy to convert and interoperate.
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.
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:
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.]
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.
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.
Ad #1: And bring down the whole process? Seriously, no, this is about the worse advice ever. Unless you are writing a tiny binary that works all on its own and on a single job/item, this is about as close as you can get to a mortal sin in any medium- or large-sized program.
Figure out what the remaining viable state of the program is, report the error (yes, loudly!), and recover. This would be the far more correct advice for #1 (except for tiny binaries doing a single job only, as said).
Whilst your application is in shatters for minutes or hours, your customer hotline going crazy, and your CFO will hate you for having to return thousands of good $$$ to your company's clients for the outage, to make them happy again.
Trust me, you don't want to bring down the whole house just to fix a pipe...
Ultimately high availability (in this context) is about making your software correct. When your software is correct you won't have availability issues because of bugs.
Meanwhile if your software does crash (or terminate in a controlled fashion) but you need high availability you'll need to restart it automatically.
There is no such thing as an "orthogonal problem" here, both things connect. That's why you (a) want to have your processes staying alive in about any software as long as possible (hence invalidating your argument for #1), and (b) as I clearly mentioned, you do need to report such unexpected errors to the developer of the program, to avoid that they get swept under the rug.
But handling cases from #1 as you described is nonviable in (virtually) all cases (except as described: if the error prevents the binary from completing that single job it was run on). Just imagine any of the programs you are running on your machine right now would just crash because they are not resilient to an off-by-one fencing error or whatever other bug (and there will be issues, even in your three-liners). I bet you'd be pretty pissed off if your programs would crash on you every other day or so...
Whats the point of a program producing incorrect results? Whats the value of that?
Let's have an example.
Person& findPerson(key_t key) {...}
Now this is a core function, the basic precondition of the function is that the key is valid and exists.
The function is called with a key that doesn't meet the precondition. A programmer who wrote the client code has made a bug.
How do you deal with this situation?
Throw and exception? Change the signature and return an error code + person? Great, now you've turned a bug into an "error", masking the actual problem.
Then what does the calling code do? How is it prepared to handle "bugs". What does the logic to deal with bugs look like? Then you write bugs in your code trying to deal with bugs?
Alternatively (I know some big orgs do this..) they put in a log and then return some "default object" and pretend there was no problem. Now how's the calling codes computation going to go, it's given bogus data to deal with.
Bottom line, I want my programs to be bug free and correct.
I don't want to even try to clutter my code with trying to write logic to deal with bugs.
What I want on every bug is clean termination, program state and stack trace. It makes it very easy to fix the bug thus actually improving my software quality.
I'm happy to know that my software actually has very few bugs.
I know that you might think that it's inconvenient for the user to have his software abort. But if the software produces corrupt results then whats the value of that?
Even worse, the user can think that results are correct even though it's garbage.
Seriously, going back to the example. iF the precondition is validated you abort. Your developers can easily fix the offending code, release a new version of the software and your system is just that much better.
If you incorporate this methodology from the start of the project you can greatly improve your quality in general.
For your core argument to hold, terminating a process would have to lead to software with fewer bugs than software that that can report and recover from bugs. I don't see how that would be the case.
Second, you did not address my main argument, that anybody (including you, your code's customers/users, etc.) would be very annoyed if our software were to crash on every remaining issue in our programs. And that argument goes as far as that the behavior you suggest could lead to us not using that service, software, OS, or even computers as a whole.
Generally terminating is a "very bad" behaviour. If you do not want it in production, better invest in ACID semantics for your application state and restore it on crash.
Much more elegant than wrong behaviour, much more user friendly.
This is typical for modern mobile applications which are supposed to be killable and restartable at any time.
In other words, make application idempotent for any start condition.
I think my customers would be quite annoyed if they were served junk data. The only result they care about is getting the correct output.
And I'm not saying it will necessarily lead to fewer bugs. What I'm really saying is that allows one to debug identify and fix the bugs easily, or easier than some other alternatives such as printing some error message to some log and then (possibly) continuing in UB land while silently corrupting state and then finally causing some new weird error (or crash if you're lucky).
So what I'm really advocating that dumping the core gives you two benefits:
- simplifies your code when you don't try to write logic to deal with broken logic itself.
- simplifies bug detection, analysis and fixing.
Together these two translate to more correct programs assuming that you actually go and fix the problems
Also you didn't answer my question. I'd like to see what kind of kludges you'd recommend.
Just to clarify: I also don't think you should bring down the whole house when no financial interests are associated with the process dying. But when money gets involved, people typically start to behave and act more professionally, so its probably a good example, even for "non-financial cases."
> passing y=0 the function is clearly a bug. It'd be much better to core dump here.
I don't follow, for me it's not clear at all. What if y is user provided? Then the argument should be checked before calling your function. Then it's checked again inside the function.
> Well, the bug here is that user input wasn't checked.
It will just result in your minefield function being wrapped inside safe variant which just returns an error in some form. Which could happen in the first place.
Seriously the best thing is throwing an exception on the bug path too. If it's truly a bug, then it shouldn't be handled and you get what you just described.
If it's a bug that could be constrained into a subsystem, you can maybe get away buy bringing down the subsystem only. That's what actually happens with your strategy too, but on the process/OS/service hypervisor level.
"Seriously the best thing is throwing an exception on the bug path too. If it's truly a bug, then it shouldn't be handled and you get what you just described."
Except that you don't get a call stack (and process state), which is like the best feature of a core dump. Just open it in your debugger and you're immediately looking at a whole lot of more of information about what happened than what an exception can tell you.
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.
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.
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.
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.
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.
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.
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.
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.
67 comments
[ 3.0 ms ] story [ 103 ms ] threadIt works well when combined with the some usual techniques like the error singletons and exceptions. You can also set an error code you need by capturing the result variable. Or just do what you need in the handler itself.
Returning a special type is about as annoying as returning and handling an error code.
- Callback-oriented programming is its own deep can of worms. What do you want to inline where? In case of leftMap, it's easy for a compiler to inline flatMap, because it's small. What if you're providing a lambda to a huge function? What about variable captures? Really, we've been there with JavaScript.
- It will either incur a yuuge runtime overhead or will require a new calling convention.
- What if you want to transform your data multiple times? Will you use nested lambdas?
IMO, using sum types is the best solution to this problem. Sum types are not "weird special type", they don't require "weird calling convention", C++ is weird for NOT supporting them.
What runtime overhead, a function call? Pointer dereference? Even template magic on the error monad type won't save you from that one. The problem is shifted to the dereference. There you get either to handle an exception or error code, or explicitly check the state of the object.
Nested lambdas do work reasonably well.
But remember, were generally talking about error handling, not a callback that is supposed to run big transforms.
At worst the overhead is a function call anyway and inhibition of inlining in the error path. Generally nothing to worry about.
But not in the standard library. Of course, in C++, you can do anything if you venture outside the standard.
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.
The pointer dereference and call overhead is not big and error handling code is supposed to be cold anyway.
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...
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.
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...
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."
CS student here, can you elaborate on this? What code would you wrap in a try block if not code that could throw an exception? Are you suggesting try blocks should only be used to insulate function calls we know to be unsafe?
C++ programs normally throw an exception when a C program would call exit() or abort().
So I would start by having one try block that surrounds your main() function with a catch-all block that prints some sort of error message before your program terminates. This is often all you need.
If a situation arises in which you can handle an exception without terminating your program, you can throw a try block in there too.
Exceptions automatically provide for you what you would write by hand in languages like C:
* Clearly, if you don't use built-in exceptions, you have to manually simulate the exception mechanism. The most popular strategies for simulating exceptions involve some combination of systematically returning and checking error codes or systematically setting and checking global error flags. I've demonstrated both above. The linked article is an example of the first strategy.Returning error codes prevents functions from directly returning their results. The "eithers" strategy is an attempt to make this a little smoother, but it doesn't solve the problem that it makes your code weird. Global error flags have their own host of problems. If you fail to check for errors, havoc will ensue. Furthermore, multithreaded environments turn global error flags into a tangled web of deceit.
Nevertheless, these ad hoc strategies often suffice because the unusual nature of exceptional errors can prevent error handling code from clogging up your primary code path too badly. But even for simple situations, the compiler can do better than you can. Why add unnecessary complexity to your code?
You should write try/catch blocks only when you know how to cope with exceptional errors. Needless to say, they should be pretty rare. But lets say that when a particular invocation of foo() fails, we want to use the maximum integer value as our result. Here's how it looks:
* Hmm... The exception version doesn't look that much simpler. After all, the assert() call will be compiled out of release builds. But C++ compilers can, and most modern ones do, generate code for the exception version as if it were written more like this: That's right. Even in the presence of a try/catch block, the code for foo is generated without the try...Unwinding the stack through native library or language boundaries is fraught with potential problems and language interop APIs have to be designed carefully round that sort of thing.
I like exceptions, because they are magic. While prototyping or for small utilities it's perfectly OK to "crash" on any error - nice error message can help then.
However I prefer when the final version handles all errors explicitly. I want to see it documented in code that error handling was not forgotten and it was deliberate to push certain errors back the stack. Of course it can work with documentation telling that this library never catches any errors, but it will put the burden on me. I also prefer to have a code, because documentation, more often then not, is lying.
Having written all that, I'm wondering how exceptions work with Python's "Explicit is better than implicit". I guess in case of exceptions this rule has lower priority than some other rule that priorities simple looking code. That would align with my use of Python - strictly for one off utils and prototypes.
Which is why I actually like Java's Checked Exceptions, at least for public-API purposes.
It's really the exact same principle as having a contract for functions' return-values.
I'm pretty sure it was proposed more than once in the standard committee.
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; }
I wouldn't say that they are doing it wrong. They throw an exception because they don't want to make assumptions about how you would want to handle the error.
What would you prefer the std::stoi() function to do?
Or in C++17, return `std::optional<int>`.
Furthermore, if you don't check the "success" flag, then your function would fail silently. The exception ensures that something reasonable happens (program termination) when the client does nothing to handle the error.
Exceptions are cleaner.
This programming style is very specific to the language. In another language, exception may be used more widely.
The upside is that the happy path (without exceptions) can be branch-free (or at least contain fewer branches than equivalent code handling errors), which is beneficial to performance (hot path stays hot).
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.
Why would you want to restart same code after an exception anyway? Conditions have changed...
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.
(A Rust-like `std::variant` return would be better here but we don't use C++17 yet in this codebase.)So I think exceptions are a good thing for unexpected outcomes; Resource errors, programming errors, etc. The problem is, we use them in cases where the error is, technically speaking, a possible, or "expect-able", outcome (such as the classical example of de-serializing data). In those cases, I fully agree, a type-level, explicit handler using the Either monad would be better.
I even think, the "expected case" is why Java introduced the notion of annotating exceptions. But as there are two different use-cases, this did not mix well...
Such a design forces you to handle all errors near the place they occurred without the syntactic overhead. (Threading problems are overrated. Easy to dodge by providing a Context attribute which then works like IO monad in Haskell.)
It essentially works like exceptions without a forced stack unwind and return - which may or may not be good. Both are easy to convert and interoperate.
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.
[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...
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.
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.
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.
Figure out what the remaining viable state of the program is, report the error (yes, loudly!), and recover. This would be the far more correct advice for #1 (except for tiny binaries doing a single job only, as said).
Seriously, in some cases when you're going for example OOB, the only reasonable action is to dump core.
Trust me. This will improve your program, not make it worse.
Trust me, you don't want to bring down the whole house just to fix a pipe...
Ultimately high availability (in this context) is about making your software correct. When your software is correct you won't have availability issues because of bugs.
Meanwhile if your software does crash (or terminate in a controlled fashion) but you need high availability you'll need to restart it automatically.
But handling cases from #1 as you described is nonviable in (virtually) all cases (except as described: if the error prevents the binary from completing that single job it was run on). Just imagine any of the programs you are running on your machine right now would just crash because they are not resilient to an off-by-one fencing error or whatever other bug (and there will be issues, even in your three-liners). I bet you'd be pretty pissed off if your programs would crash on you every other day or so...
Whats the point of a program producing incorrect results? Whats the value of that?
Let's have an example.
Now this is a core function, the basic precondition of the function is that the key is valid and exists. The function is called with a key that doesn't meet the precondition. A programmer who wrote the client code has made a bug.How do you deal with this situation?
Throw and exception? Change the signature and return an error code + person? Great, now you've turned a bug into an "error", masking the actual problem. Then what does the calling code do? How is it prepared to handle "bugs". What does the logic to deal with bugs look like? Then you write bugs in your code trying to deal with bugs?
Alternatively (I know some big orgs do this..) they put in a log and then return some "default object" and pretend there was no problem. Now how's the calling codes computation going to go, it's given bogus data to deal with.
Bottom line, I want my programs to be bug free and correct. I don't want to even try to clutter my code with trying to write logic to deal with bugs.
What I want on every bug is clean termination, program state and stack trace. It makes it very easy to fix the bug thus actually improving my software quality.
I'm happy to know that my software actually has very few bugs.
I know that you might think that it's inconvenient for the user to have his software abort. But if the software produces corrupt results then whats the value of that? Even worse, the user can think that results are correct even though it's garbage.
Seriously, going back to the example. iF the precondition is validated you abort. Your developers can easily fix the offending code, release a new version of the software and your system is just that much better.
If you incorporate this methodology from the start of the project you can greatly improve your quality in general.
Second, you did not address my main argument, that anybody (including you, your code's customers/users, etc.) would be very annoyed if our software were to crash on every remaining issue in our programs. And that argument goes as far as that the behavior you suggest could lead to us not using that service, software, OS, or even computers as a whole.
This is typical for modern mobile applications which are supposed to be killable and restartable at any time.
In other words, make application idempotent for any start condition.
And I'm not saying it will necessarily lead to fewer bugs. What I'm really saying is that allows one to debug identify and fix the bugs easily, or easier than some other alternatives such as printing some error message to some log and then (possibly) continuing in UB land while silently corrupting state and then finally causing some new weird error (or crash if you're lucky).
So what I'm really advocating that dumping the core gives you two benefits:
- simplifies your code when you don't try to write logic to deal with broken logic itself.
- simplifies bug detection, analysis and fixing.
Together these two translate to more correct programs assuming that you actually go and fix the problems
Also you didn't answer my question. I'd like to see what kind of kludges you'd recommend.
I don't follow, for me it's not clear at all. What if y is user provided? Then the argument should be checked before calling your function. Then it's checked again inside the function.
Well, the bug here is that user input wasn't checked.
Then it's checked again inside the function
Checking twice is better than never checking at all (i.e. in the case argument validation didn't happen outside of the functiion)
It will just result in your minefield function being wrapped inside safe variant which just returns an error in some form. Which could happen in the first place.
Seriously the best thing is throwing an exception on the bug path too. If it's truly a bug, then it shouldn't be handled and you get what you just described.
If it's a bug that could be constrained into a subsystem, you can maybe get away buy bringing down the subsystem only. That's what actually happens with your strategy too, but on the process/OS/service hypervisor level.
Except that you don't get a call stack (and process state), which is like the best feature of a core dump. Just open it in your debugger and you're immediately looking at a whole lot of more of information about what happened than what an exception can tell you.
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.
Works out fantastic.
Note that in c++, the absence of algebraic data type made it a little bit hard to understand how Either is coded.
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...
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.
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.
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.
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.