C vs C++, from the author of ZeroMQ



  • Liked, mainly because of the excellent choice of sentence. Anybody who hasn't had to deal with compile errors from boost::spirit really has no clue about the true depths of horror that lurk within a C++ compiler.


  • Banned

    @Jaloopa said:

    To be a proper EWW, this should have called me racist

    Yeah, and my post doesn't contain lap dance.


  • Banned

    @Jaloopa said:

    It was mentioned, wasn't it?

    Your law talks about turning into. This thread was about microoptimizations all along.

    @Jaloopa said:

    I don't get worked up about exactly what happens under the hood unless I need to to know for the performance improvement I'm chasing

    Well, some people do it for fun.

    @Jaloopa said:

    UK slang. Short for "isn't it"

    I'd let it pass if it was valid word in Polish, in analogy to "wanna" which in Polish means a bath tub.

    @Jaloopa said:

    C++ was the first language I used.

    I feel so sorry for you. It's a terrible first language.

    @Jaloopa said:

    Damn, you really hate Java don't you?

    Hell yeah. I've always hated it, and now that I actually learned it, I hate it even more.

    @Jaloopa said:

    Don't make stupid bets then

    Until now, I was winning 315:0.



  • @Bulb said:

    No; if the header had full code for inlining, it could be inlined. But the header does not have it.

    That's nice. I was talking about what it could hypothetically do. And the source code could certainly be added to the header. Or the libraries could be distributed with LTO-enabling IR. Or LTO could learn to inline from the binary code.

    @Bulb said:

    qsort does not. std::sort does, though. They are otherwise quite similar code.
    I was talking about f'ing qsort. I explicitly said that you'd get multiple instantiations in idiomatic C++ code (i.e. where you would pass a functor).

    @dkf said:

    There's already people working on it as part of LLVM
    Is there a name I can look up? I might be interested in skimming a paper or two, and a 10-second Google search doesn't turn up anything relevant-looking.

    @Gaska said:

    Inlining dynamic libraries kinda violates API contract.
    Citation? E.g., how does that interact with the memset/etc. example from @cvi?

    I'm not necessarily distrusting; I know a lot about the C and C++ standards but my knowledge drops off a lot when it comes to things like what POSIX or anyone else says about how DSOs work.


  • Banned

    @EvanED said:

    Citation?

    I said, kinda.

    @EvanED said:

    E.g., how does that interact with the memset/etc. example from @cvi?

    memset() and some other similar functions are special in that gcc treats them as built-ins. But in general case, inlining dynamic library makes it impossible to replace the library with another version, which is one of the only two reasons why people even bother with dynamic libraries, the other not being relevant for years now. Also, LGPL explicitly states that inability to replace library with another version is violation of license.


  • Discourse touched me in a no-no place

    @EvanED said:

    Is there a name I can look up? I might be interested in skimming a paper or two, and a 10-second Google search doesn't turn up anything relevant-looking.

    Not that I can remember. It was something I turned up in a Google search around 6 months ago, and it was only very tangential to what I was searching for at the time (details of how to make JITting actually work right for me) so I didn't pay much attention. I have a good memory for some things (e.g., capabilities of things) but terrible for others, especially names of people! I almost have to use the exact reverse of the tricks most people use to memorise stuff. 😕

    I think it might have been a project at CMU? (Student projects don't always work out, so who knows if this one was a bust or not?)


  • Banned

    @dkf said:

    I have a good memory for some things (e.g., capabilities of things) but terrible for others, especially names of people!

    Damn, I know that feel. Back in high school, I didn't know names of half the people I regularly hanged out with - including my best tobacco supplier!



  • @Gaska said:

    C++ CODE REVIEWS(in a project using boost::spirit)

    Enlighten me?



  • I thought the article was a bit weird, considering that this was supposedly an experienced c++ developer, talking about how a home brewed linked list has better performance than a library function. ‘Everybody knows’ linked lists have terrible performance due to caching concerns don't they? So why even pick that as an example.

    But then it hit me, and the answer's so blindingly obvious I could kick myself: ZeroMQ is a message queue.


  • Discourse touched me in a no-no place

    @Gaska said:

    Also, LGPL explicitly states that inability to replace library with another version is violation of license.

    But if it were done by the dynamic library loader itself, it wouldn't be a problem. The Freedom would be correctly preserved, since you'd be able to supply the library you wanted to and have it get linked in as expected; it'd just be a more complex mechanical load/link step than the very simple version most people have been using to date. The result would be a derived work to the same degree as what happens now; LTO does not meaningfully change an API.

    I suspect it would be simpler if the code wasn't converted on from the IR, or if the IR was retained in the object files/libraries. I think it might be possible to do such thing, but I don't know for sure.


  • Banned

    @swayde said:

    Enlighten me?

    Boost is probably the most complicated piece of template magic in universe. And it's huge, with hundreds of submodules. Spirit is one of largest submodules, so large that it has a whole website with it's own domain, separate from Boost's main one, dedicated only to just that one module.

    All the above, times the number of different template specializations.


  • Banned

    @dkf said:

    But if it were done by the dynamic library loader itself, it wouldn't be a problem.

    Yep, you're right. I think. I'd recommend to consult your lawyer.


  • Winner of the 2016 Presidential Election

    @Gaska said:

    Boost is probably the most complicated piece of template magic in universe. And it's huge, with hundreds of submodules. Spirit is one of largest submodules, so large that it has a whole website with it's own domain, separate from Boost's main one, dedicated only to just that one module.

    All the above, times the number of different template specializations.

    The mere thought of the possibility of ever having to debug anything like that makes me want to shoot myself.



  • This rant makes me want to stay away from ZeroMQ. What annoys me the most is that his comparisons of the "problems" of C++ and the "solutions" in C are not equivalent.

    For example, he complains about how destructors can't throw if you have exceptions enabled, since if an exception escapes a destructor while another exception is tearing down the stack, your program will call terminate:

    Moreover, even if initialisation wasn't a problem, termination definitely is. You can't really throw exceptions in the destructor. Not because of some self-imposed artificial restrictions but because if the destructor is invoked in the process or unwinding the stack and it happens to throw an exception, it crashes the entire process.

    (He says it crashes, but that's not technically true. It calls terminate, which you can replace with code of your own. You could have terminate call main, for example, and just start right back up :P) Anyway, he claims that this is not a problem in C, because...

    Compare the above to the C implementation. There are only two states. Not initialised object/memory where all the bets are off and the structure can contain random data. And there is initialised state, where the object is fully functional. Thus, there's no need to incorporate a state machine into the object:

    Except that's not the same "problem" he described before at all. If your destructor can fail, then your terminate function can fail too. Replacing a destructor that can fail for a termination function that can't is not equivalent. To be fair, you need a handler for the terminate function too. But then, what if THAT handler fails? And so on. The problem is the same. Disabling exceptions just lets you ignore it.

    Which means that this guy doesn't just not understand exceptions. He doesn't understand how error handling works at all. Returning errors is only easier than exceptions if you are doing it wrong.

    Error returns and exceptions are equivalent. The advantage that exceptions offer is that they are automatic and require effort to do the wrong thing, while error returns are manual and do the wrong thing by default. But some people treat exceptions as if they were this magical thing that just happen for no reason.

    If a certain line can throw an exception, it doesn't mean that without exceptions the line would work. Exceptions aren't there just to fuck with your code. If a line would throw an exception, then without exceptions you would have to check for an error return. Which means that every line in your code (except for things that promise not to ever fail) have to be of the form

    if (Function() != SUCCESS) goto cleanup;
    

    If you've ever had the "pleasure" of working in a codebase that deals with error returns properly, you'll see that they don't do as his trivial example showed, and handle the error inline. What usually happens is that they clean up whatever resources they had aquired, then returned the error to the calling function.

    Now, what does an exception do, when the code properly follows the RAII idiom? It cleans up resources and returns the error to the calling function. Exceptions and error returns do the same goddam thing.

    The difference is that with exceptions, if you reach a certain point of execution, you can be relatively certain that everything up to that point worked ok (there's always the chance that some genius swallowed the exception and effectively did ON ERROR RESUME NEXT). With error returns, you have to hope that no one ever missed a single error return.

    The problem of destructors not being allowed to let exceptions escape is the same as the cleanup not being allowed to generate it's own errors.

    And one other thing. It's not that destructors aren't allowed to throw, as he claimed. Destructors can throw, and it's fine. Destructors aren't allowed to have any exceptions thrown inside of them escape their scope. So if you tried to do something in the destructor and failed, your choices are to swallow that exception or terminate the program. But that is also true with error returns.



  • And another thing. His examples also show that he doesn't know how to use exceptions in the first place.

    The problem is that there are two distinct reasons that you may want to use exceptions. One is during development, to catch logic errors in your code. It's trivial to create a macro that checks for example your arguments and throws if an argument to a function doesn't comply with some requirement. You can then disable the macro in the release build, so the release build is optimized for performance, and the debug build always checks that you don't make logic errors, like trying to access the tenth element of an array with nine elements.

    You don't need to catch those exceptions. Just let them kill the program, and look at the log that tells you "line x in file y.cpp failed because <\condition> == false". You now know where your bug is first discovered and can fix whatever failed. You could also have the exception capture a backtrace or whatever else you want.

    The other reason to use exceptions is for errors on your release build. The good thing, however, is that if you used the debug exceptions correctly, you should only find exceptions when interfacing with the "outside". Calls to libraries, to the OS, etc. So basically, release build exceptions should only exist at the borders of your code, and their job becomes a lot simpler: decide how you will react when the outside world craps out on you.


  • Discourse touched me in a no-no place

    @Kian said:

    Calls to libraries, to the OS, etc.

    Every single place where an object is created. You might well wrap it in a smart pointer of some kind — I've seen it claimed that doing it always is best practice, and would certainly concur that it's usually a good idea — but anywhere where new is called can throw an exception if things get memory-stressed. It's not a bad thing that this happens (being able to assume that construction succeeded in the rest of your code is a good thing) but it does mean that you really have to care about them in many places.

    Most code just doesn't care. Code that's trying to be extremely robust so that other (less robust) things can be safely built on top of it does have to care. That's what the blog-writer is doing…



  • @Kian said:

    And another thing. His examples also show that he doesn't know how to use exceptions in the first place.

    The problem is that there are two distinct reasons that you may want to use exceptions. One is during development, to catch logic errors in your code. It's trivial to create a macro that checks for example your arguments and throws if an argument to a function doesn't comply with some requirement. You can then disable the macro in the release build, so the release build is optimized for performance, and the debug build always checks that you don't make logic errors, like trying to access the tenth element of an array with nine elements.

    You don't need to catch those exceptions. Just let them kill the program, and look at the log that tells you "line x in file y.cpp failed because <\condition> == false". You now know where your bug is first discovered and can fix whatever failed. You could also have the exception capture a backtrace or whatever else you want.

    The other reason to use exceptions is for errors on your release build. The good thing, however, is that if you used the debug exceptions correctly, you should only find exceptions when interfacing with the "outside". Calls to libraries, to the OS, etc. So basically, release build exceptions should only exist at the borders of your code, and their job becomes a lot simpler: decide how you will react when the outside world craps out on you.

    So you're saying that good code should never throw an exception? Your second paragraph actually describes assertions, a language feature that C does have. Your final paragraph states that there is no other time when it appropriate to throw an exception; only to handle exceptions that other people's code has thrown.

    @Kian said:

    This rant makes me want to stay away from ZeroMQ.

    Ok, being good at one thing doesn't necessarily make you good at something else, so I can't say for sure that this guy is a good low-level programmer. And to be honest, I've never used 0mq, so I can't even vouch for that myself. But from what I've read, 0mq is a really wonderful piece of technology. They took an incredibly complex problem and provided a simple, open-source solution that outperforms every other billion-dollar monstrosity that their competitors ever provided. So you'd need a better reason than ‘doesn't know how to use exceptions’ to credibility criticize this guy's ability to produce good software.

    And even if it does turn out that he's better at designing such systems than implementing them, you're in luck: ZeroMQ is apparently one of those projects that has been rewritten in every programming language under the sun.


  • Discourse touched me in a no-no place

    @Buddy said:

    They took an incredibly complex problem and provided a simple, open-source solution that outperforms every other billion-dollar monstrosity that their competitors ever provided.

    That just tells us that there are a lot of bad programmers out there. We knew that.



  • I don't think I've been confused by or had problems with exceptions, ever. Ever. Like, seriously, I didn't know it was possible for an experienced programmer to be confused by or distrust them.



  • @dkf said:

    Every single place where an object is created.

    That's an exaggeration. Most of your code should not be allocating stuff on the heap. You can get a lot of mileage out of the stack. And if you're trying to do super resilient stuff, you probably aren't calling the default new. You can allocate a big chunk at startup and use your own allocators from those chunks, and then you're not asking the OS for memory any more.

    And even if you were, that goes back to my point about how you have to decide what to do when the outside craps out on you. Most programs want to quit when they fail an allocation, because they're not critical stuff. For those, a simple empty catch block in main will generally perform a clean shut down if you ran out of memory. Let the exception shut you down and be done. If you have a strategy for what to do when you run out of memory, then implement it. But that's something that you'll only need to think about in a few places. You shouldn't have catch blocks everywhere, for each function that might throw. You set checkpoints at certain places where it makes sense to try to recover.

    @Buddy said:

    So you're saying that good code should never throw an exception?
    Tested code should not throw exceptions, yes. If it does, fix it until it doesn't.

    @Buddy said:

    Your second paragraph actually describes assertions, a language feature that C does have.
    Huh, I thought I mentioned that it mimics asserts, except it gives you a bit more control. Must have edited it out. Anyway, no it doesn't describe asserts. Asserts compare a scalar type against 0, while I prefer using bools for conditionals, and calls abort. I proposed throwing an exception and replacing terminate so you can get better diagnostics. Completely different.

    @Buddy said:

    Your final paragraph states that there is no other time when it appropriate to throw an exception; only to handle exceptions that other people's code has thrown.
    Exactly.

    I'll clarify, because I catch a hint of skepticism. If your own code would throw, it means you have a bug. Trying to protect against your own bugs is a losing proposition. To protect against your own bugs, you have to add code and increase complexity. Adding code and increasing complexity is the primary source of bugs. So in trying to protect against your own bugs, you are likely introducing more bugs. Its a spiral of fail.

    Yes, your code will have bugs. It will even if you try to recover from them. If you don't try to recover, you'll get consistent bug reports and you'll be able to more quickly track down the bugs and fix them. If you try to recover, you'll get weird bug reports for things that sometimes fails, because your bug catching code is recovering, sometimes, and creating complex interactions between working and non working code, and figuring out where the original bug was becomes a huge pain.

    @Buddy said:

    So you'd need a better reason than ‘doesn't know how to use exceptions’ to credibility criticize this guy's ability to produce good software.
    The guy wants to build resilient software, but doesn't understand error handling. Error handling is the no.1 job of resilient software. Maybe he can make good software, I don't know. But considering his choice of snippets, such as std::list<person*>, I doubt it. That alone tells me to stay the fuck away.

    @Buddy said:

    ZeroMQ is apparently one of those projects that has been rewritten in every programming language under the sun.
    Oh, that's cool. Still, I'll probably stay away from it just because I don't know what it does and it's unlikely I'll need it :P


  • Banned

    @Kian said:

    Which means that this guy doesn't just not understand exceptions.

    Yes.

    @Kian said:

    He doesn't understand how error handling works at all.

    Yes.

    @Kian said:

    Returning errors is only easier than exceptions if you are doing it wrong.

    No. From the callee's point of view, they are equivalent - early exit if something bad happens, both can have some value. From the caller's point of view, error returning is similar to exception throwing, except uncaught exception will blow up your program instead of silently continuing in erroneous state. The flip side is, if you don't know absolutely every single exception that might be thrown by absolutely every single thing in your function body, you cannot guarantee exception safety in any way, nor specify a finite set of exceptions that client code has to expect your code to throw. This might backfire in most peculiar ways. Also, you don't have to constantly guess where the program execution will jump if something bad happens - it's always immediately after the given function.

    @Kian said:

    The advantage that exceptions offer is that they are automatic and require effort to do the wrong thing

    If by the "wrong thing" you mean not handle the error, then no, not handling is far easier than handling.

    @Kian said:

    f a line would throw an exception, then without exceptions you would have to check for an error return. Which means that every line in your code (except for things that promise not to ever fail) have to be of the form
    if (Function() != SUCCESS) goto cleanup;

    Equivalent Rust code that uses return values only and doesn't have exceptions at all:

    try!(Function())
    

    If you did your RAII correctly, cleanup works automatically.

    @Kian said:

    The other reason to use exceptions is for errors on your release build. The good thing, however, is that if you used the debug exceptions correctly, you should only find exceptions when interfacing with the "outside". Calls to libraries, to the OS, etc. So basically, release build exceptions should only exist at the borders of your code, and their job becomes a lot simpler: decide how you will react when the outside world craps out on you.

    The problem is, if you want to show that transferring a file failed in the middle of operation, the ConnectionTimedOutException will have to fly at least a dozen function calls before it can be handled (assuming a good codebase of a rather complicated project with proper separation of concerns).

    @Kian said:

    a simple empty catch block in main

    I hate you.

    @Kian said:

    Tested code should not throw exceptions, yes. If it does, fix it until it doesn't.

    I think you don't get what exceptions are for either. Shit happens, and you need to account for it. Exceptions are one way of handling a situation where, for example, a user has disconnected a gamepad while you were reading input.

    @Kian said:

    Anyway, no it doesn't describe asserts. Asserts compare a scalar type against 0, while I prefer using bools for conditionals, and calls abort. I proposed throwing an exception and replacing terminate so you can get better diagnostics. Completely different.

    Taking a shit in public toilet is just as much taking a shit as is taking a shit in a forest. The fact I'm using leaves to wipe my ass is irrelevant.

    @Kian said:

    If your own code would throw, it means you have a bug.

    You're so full of shit right now, almost as much as that 0MQ guy.



  • @Gaska said:

    The advantage that exceptions offer is that they are automatic and require effort to do the wrong thing

    If by the "wrong thing" you mean not handle the error, then no, not handling is far easier than handling.

    I assume Kian meant "continue execution as if there were not an error."

    With return codes, doing that is the "default" and you need to take explicit action to not do that. With exceptions, it's the opposite.



  • @Gaska said:

    The flip side is, if you don't know absolutely every single exception that might be thrown by absolutely every single thing in your function body, you cannot guarantee exception safety in any way, nor specify a finite set of exceptions that client code has to expect your code to throw.
    This is just plain wrong, on multiple levels. First, you assume that unknown errors is not something that can happen with error codes. One could argue "if you don't know every single error code that might be returned by every single call in your function body, you cannot guarantee a safe cleanup in any way, nor specify a finite set of error codes that client code has to expect your code to return".

    Second, you can guarantee exception safety, through RAII, and you don't have to care what kind of exception was thrown. Just as you wouldn't have to care what sort of error was returned to do a safe clean up. You can in fact document the exact level of exception safety that any given function provides: basic safety, meaning you don't leak resources and objects are left in unspecified but valid state; strong safety, meaning the function either succeeds or doesn't modify any objects; and no throw, meaning your function always succeeds. This is orthogonal to what exception was thrown, and how you'll handle it.

    Finally, the language provides the means to do a "catch-all" for unknown exceptions. If your custom exceptions inherit from the base std::exception, then you can have a back up catch(std::exception& e) to catch anything that the more specific handlers didn't. And you get a virtual what() method to return a string of what caused the exception.
    @EvanED said:

    @Gaska said:
    If by the "wrong thing" you mean not handle the error, then no, not handling is far easier than handling.

    I assume Kian meant "continue execution as if there were not an error."

    Exactly. Most functions should just let errors flow through them, not catch them and try to fix them. Responding to an error is not always the right thing to do. Ignoring it is always the wrong thing to do.

    @Gaska said:

    Equivalent Rust code that uses return values only and doesn't have exceptions at all:

    try!(Function())

    If you did your RAII correctly, cleanup works automatically.


    Equivalent C++ that does the same thing with exceptions:

    Function();
    

    The point is, you still have to write try!() for every call, and that's just noise. Exceptions do the same thing, except they do it automatically everywhere for you, and you don't have the chance of forgetting it and swallowing an error.

    @Gaska said:

    The problem is, if you want to show that transferring a file failed in the middle of operation, the ConnectionTimedOutException will have to fly at least a dozen function calls before it can be handled (assuming a good codebase of a rather complicated project with proper separation of concerns).
    This is the same with an error return. The error will have to be passed from function to function for a dozen calls before there's someone that can handle it.

    The exception just gets you there automatically, while a single missed error check means you now have a bug.

    @Gaska said:

    Exceptions are one way of handling a situation where, for example, a user has disconnected a gamepad while you were reading input.
    And how is reading input from a gamepad not "interacting with the outside world"?

    @Gaska said:

    Taking a shit in public toilet is just as much taking a shit as is taking a shit in a forest. The fact I'm using leaves to wipe my ass is irrelevant.
    Hmm, should I have added a 🚎 after the "completely different" comment?


  • Discourse touched me in a no-no place

    @Kian said:

    Most of your code should not be allocating stuff on the heap.

    You can run out of stack space too. In fact, you can run out of it very easily with some algorithms in high-thread configurations. There are ways to work around that, but they're somewhat complicated and not technically portable. 😄

    It's a fact of life that allocation of a resource can fail. It's also one of the more awkward things to recover from when it isn't a large block of memory that you couldn't grab; failing to get a few GB is usually recoverable from, whereas failing to get 16 bytes isn't. Very few software systems recover gracefully when it happens. Not that many are even checked for what happens when it occurs…



  • Yes, but then we're back to people complaining about things that error returns can't solve either. What error code do you return for a stack overflow, and how?


  • Discourse touched me in a no-no place

    @Gaska said:

    The problem is, if you want to show that transferring a file failed in the middle of operation, the ConnectionTimedOutException will have to fly at least a dozen function calls before it can be handled

    So what? The error condition should be handled at the point when you can do something meaningful about it. Sometimes, that's just “log it and keel over dead”, alas, but often programs can recover better than that for at least some types of failures.

    Now, I don't really like RAII — I think it's a hack to simulate what you might do if you had proper user-controlled blocks, and it hides that you're doing this sort of resource management (which is the cardinal sin in my eyes) — but it's an effective way to make C++ code work right. I like trickery to be visible. :D



  • @Kian said:

    stack overflow, and how

    The one you defined? E_STACK_OVERFLOW? I know you can catch the exception in.net.
    Edit: you could. Can't now it seems.



  • Humor me. Write a piece of code that would check and return E_STACK_OVERFLOW. I'm curious what that looks like.



  • @Kian said:

    Asserts compare a scalar type against 0, while I prefer using bools for conditionals, and calls abort.

    Meh, this is a C/C++ pecularity; you can use the standard assert with boolean expressions just fine.

    @Kian said:

    I proposed throwing an exception and replacing terminate so you can get better diagnostics. Completely different.

    The standard assert with GCC prints source file & line, and the expression of the assertion. But, yeah, a lot of projects use a custom assert with a bit more diagnostic information or with a debug break instead of terminating the program.

    I rather prefer the latter (debug break) over throwing exceptions, though. Especially if the debug break is set up properly (i.e., it doesn't cause the compiler to assume code following it is unreachable and therefore eligible for DCE).

    (Also, I prefer std::abort() over throwing an exception in an assert or assert-equivalent, because I want to fail fast and typically don't care about cleaning up nicely because the process is going to die very shortly anyway.)

    @Kian said:

    I'll clarify, because I catch a hint of skepticism. If your own code would throw, it means you have a bug.

    Edit: Whoops,@Kian said something similar to what I say below in an earlier post. Missed it the first time I was reading. Sorry. :-/

    No, it most assuredly does not. For example, your code might throw because it encountered an error while parsing some external data file (which, for the sake of argument, was specified by some user and downloaded from the internet). You have zero control over it's contents, so you can't test it on beforehand.

    And no, your file parser might not know if it's possible to recover from the error or not.

    Throwing exceptions is not only for bugs in your code (called "boneheaded exceptions" here), but for other errors too (look at the ones called "exogenous" in the previous link).




  • Banned

    @EvanED said:

    With return codes, doing that is the "default" and you need to take explicit action to not do that. With exceptions, it's the opposite.

    It's fault of the language, not of the approach in general. For example, in Rust, you get a warning if you don't check for an error, and if a function returns an object or an error, you can never access the object if there was an error.

    @Kian said:

    First, you assume that unknown errors is not something that can happen with error codes.

    Unknown error codes you didn't prepare for don't crash your program.

    @Kian said:

    Second, you can guarantee exception safety, through RAII, and you don't have to care what kind of exception was thrown.

    Which definition of exception safety you mean? Because I meant the "there will be no exceptions to catch except these specific ones, thrown under these specific circumstances" one.

    @Kian said:

    Finally, the language provides the means to do a "catch-all" for unknown exceptions.

    I'm SO going to wrap all my fault-tolerant functions' bodies in unconditional catch blocks.

    @Kian said:

    Ignoring it is always the wrong thing to do.

    Not always. For example, ignoring OpenGL errors is perfectly fine.

    @Kian said:

    Equivalent C++ that does the same thing with exceptions:

    Except you don't know the type of exception that might or might not be thrown, and don't know where it will be handled (or not).

    @Kian said:

    The point is, if you still have to write try!() for every call, and that's just noise.

    try!() isn't mandatory here - it's just a macro that expands to match foo() { Ok(o) => x, Err(e) => return e }. If you want to meaningfully handle the error, you'll write a usual match statement, and it won't include any noise at all (unless you consider error handling itself a noise). There are also and_then() and or_else() methods which greatly reduce number of match clauses in chains of errorable functions. Besides, Rust has much less noise than C++ anyway (mostly thanks to great type inference), so it doesn't hurt that much - especially if it's to provide better (IMO) error handling model.

    @Kian said:

    Exceptions do the same thing, except they do it automatically everywhere for you

    That's the problem.

    @Kian said:

    and you don't have the chance of forgetting it and swallowing an error.

    Unless you turn off warnings, same applies to Rust. Except Rust checks it statically, and exception needs to happen if you want to see it's not handled properly.

    @Kian said:

    This is the same with an error return. The error will have to be passed from function to function for a dozen calls before there's someone that can handle it.

    I meant that keeping all your error handling code on the API boundary only is often impossible.

    @Kian said:

    And how is reading input from a gamepad not "interacting with the outside world"?

    You should revise your statement to "tested code should not throw exceptions unless it interacts with outside world", then.

    @Kian said:

    Hmm, should I have added a 🚎 after the "completely different" comment?

    You should, because the rest of your post made me think you're totally serious.



  • @Gaska said:

    Except you don't know the type of exception that might or might not be thrown, and don't know where it will be handled (or not).

    This is what documentation is for - methods should, ideally, document exactly which exception types they can throw and why.
    I don't see how error codes are different, personally - the pattern I've often seen when dealing with stuff that returns error codes is a giant list of error codes shared by the whole application, in which case I'm never quite sure which error codes a function can return until I look at the API documentation.



  • @swayde said:

    http://stackoverflow.com/a/7067973

    You're not catching a stack overflow there. You're preventing one. And the code in the exception case would look exactly the same, only with throw instead of return. Again, when discussing exceptions versus error returns, try to provide equivalent scenarios.

    @Gaska said:

    It's fault of the language, not of the approach in general. For example, in Rust, you get a warning if you don't check for an error, and if a function returns an object or an error, you can never access the object if there was an error.

    So Rust basically implements exceptions, only it makes you work to have them work correctly instead of letting them work correctly by default. Awesome. Why are you against exceptions, exactly?

    @Gaska said:

    Unknown error codes you didn't prepare for don't crash your program.
    No, they just let it continue to run in an undefined, wrong state. SO MUCH BETTER! Who cares about data integrity anyways?

    @Gaska said:

    Which definition of exception safety you mean? Because I meant the "there will be no exceptions to catch except these specific ones, thrown under these specific circumstances" one.
    The one that is actually a term used by programmers, and not one pulled out of my ass?LMGTFY:exception safety

    First hit: http://en.wikipedia.org/wiki/Exception_safety

    @Gaska said:

    Not always. For example, ignoring OpenGL errors is perfectly fine.
    For what definition of fine? You like staring at black screens?

    @Gaska said:

    I meant that keeping all your error handling code on the API boundary only is often impossible.
    Good thing I didn't say you had to do that?

    @Gaska said:

    You should revise your statement to "tested code should not throw exceptions unless it interacts with outside world", then.
    You are right. Consider it amended.

    That aside, we seem to look at error handling from two different perspectives. I am on the camp that believes that applications should fail fast if they encounter something they don't know how to handle. For me, having the program fail gracefully and shut down when it encounters an unexpected error is a good thing. The idea is that this preserves the integrity of the rest of the system. Yes, having the program crash is a small annoyance, but it's better than the alternative, limping along in an undefined state, where you might start lying to the user because you don't actually know if that save operation actually completed successfully.

    You seem to be on the camp that wants to avoid crashing at all costs. You seem to believe that crashing is the absolute worst thing that you can do, and that anything is better than crashing. Even swallowing errors when you don't know what else to do. Because apparently the user will be happier if your program didn't crash but instead began doing wrong things. You are wrong, of course, as is everyone else in your camp.



  • @Kian said:

    For what definition of fine? You like staring at black screens?

    This is fine.



  • @Gaska said:

    It's fault of the language, not of the approach in general. For example, in Rust, you get a warning if you don't check for an error, and if a function returns an object or an error, you can never access the object if there was an error.

    That eliminates accidentally forgetting to handle it, but that's not the only problem. Even if you are diligent, checking and acting on the return code is code that you don't need to write if you're using exceptions, and it visually interrupts the flow of what you're actually doing.

    What is a language issue is that return codes have other obnoxious problems, like giving you an inability to write foo(bar()) (as if anyone one would ever want to do that!) or initializing a variable to the logical return value of a function. The latter can make it hard/obnoxious to work with types that you want to not have an "uninitialized" status, because you have to wrap them with an optional or something.

    In addition to those, return codes in practice carry an obnxiously little amount of information, and there's not a good way to add more. (I've never seen, for instance, someone return an Error* from a function where NULL is "no error".)

    I rather dislike exceptions, but for these reasons I dislike return codes even more. I think it'd probably be possible to design a language that does this right, but... I think I haven't seen it.



  • One thing I absolutely love about exceptions is stacktraces - knowing exactly where, right down to exact line numbers, an exception was thrown (EDIT: and the code path which led up to the exception) is amazingly useful (I really don't think I can overstate how useful it is). Returning error codes, in this regard, aren't nearly as useful.


  • Banned

    @Masaaki_Hosoi said:

    This is what documentation is for - methods should, ideally, document exactly which exception types they can throw and why.

    And now you have a really nasty dependency hell - the quality of your documentation strongly depends on quality of std::string::operator+ documentation, and every other function you've explicitly or implicitly used in your code.

    @Masaaki_Hosoi said:

    I don't see how error codes are different, personally

    Your application doesn't go to hell by default. And you always know exactly which functions you need to check and which you don't.

    @Masaaki_Hosoi said:

    the pattern I've often seen when dealing with stuff that returns error codes is a giant list of error codes shared by the whole application, in which case I'm never quite sure which error codes a function can return until I look at the API documentation.

    Thankfully, usually you care only about two cases - no error and any error.

    @Kian said:

    So Rust basically implements exceptions, only it makes you work to have them work correctly instead of letting them work correctly by default.

    No, it doesn't. All errors are signaled via return values. Error will never go up the call stack more than one entry per statement, and will never cause you fast-forward inside your function. They never crash your program (unless the caller calls unwrap() on result). And it utilizes exactly the same language features the functions without error modes do - Result is simple generic enum that's been made special by convention rather than the compiler - you get the same warning for any type declared with #[must_use] attribute.

    @Kian said:

    No, they just let it continue to run in an undefined, wrong state. SO MUCH BETTER! Who cares about data integrity anyways?

    Are we speaking about a case where your error handling code doesn't recognize the specific error, or a case where there's no error handling at all? Because the statement you quoted was in the former context.

    @Kian said:

    The one that is actually a term used by programmers, and not one pulled out of my ass?

    OK, you got me there. I used wrong words for things. Doesn't make my point any less valid.

    @Kian said:

    For what definition of fine? You like staring at black screens?

    OpenGL errors are unrecoverable and non-fatal - what else can you do but ignore them? I mean, you certainly could abort() or something, but would it really be better than just keep going? Though I think logging the error codes somehow is a good thing to do - but I don't consider logging to be error handling.

    I've once been installing Medal of Honor: Airborne on an old computer (even at the time). The installation took so long I started playing it before installation completed. The first level had most terrain textures missing (replaced by black void), and music wasn't there, but all in all it was playable, and I had some fun. Would it be really better if they handled missing files "correctly"?

    @Kian said:

    I am on the camp that believes that applications should fail fast if they encounter something they don't know how to handle.

    And I am on the camp that thinks the unrecoverable errors are uninteresting, so narrows down the topic to recoverable errors alone.

    @EvanED said:

    That eliminates accidentally forgetting to handle it, but that's not the only problem. Even if you are diligent, checking and acting on the return code is code that you don't need to write if you're using exceptions, and it visually interrupts the flow of what you're actually doing.

    When writing error handling code, there are four cases you might encounter:

    1. there is no error - this part of code is core, and needs to be as clear as possible;
    2. there is an error and you have to abort transaction - C++ way is RAII and exceptions, which is often implemented implicitly by the clean path and therefore it's indistinguishable from exceptions-free code; Rust way is RAII and try!()
    3. there is an error and you need to handle it somehow - C++ way is catching the exception in some random place in code, possibly multiple places; Rust way is checking the function result, often in the same expression where the error originated from (much easier to follow when reading code)
    4. there is an error and you can't do anything - C++ way is to throw uncaught exception (which is indistinguishable from the case 2), and indistinguishable from exceptions-free code); Rust way is unwrap() - a method of Result that panics if it's not Ok variant.

    Rust is slightly worse in the first case (which is outweighed by overall ergonomy gains over C++), and is IMO much better in all other.

    @EvanED said:

    What is a language issue is that return codes have other obnoxious problems, like giving you an inability to write foo(bar())

    foo(try!(bar())). Or foo(bar().unwrap()). Or foo(bar().or(0)). Or foo(bar().or_else(|| baz()). Or, hell, foo(match bar() { Ok(o) => o, Err(e) => what the fuck you want }) if you're such a special snowflake.

    @EvanED said:

    initializing a variable to the logical return value of a function

    See above.

    @EvanED said:

    In addition to those, return codes in practice carry an obnxiously little amount of information, and there's not a good way to add more. (I've never seen, for instance, someone return an Error* from a function where NULL is "no error".)

    You would have the same problem if you threw around integers. In Rust, you don't have error codes but errors - as in, whole objects that can be anything you want. Result is nothing more than tagged union of two arbitrary types.

    @Masaaki_Hosoi said:

    One thing I absolutely love about exceptions is stacktraces - knowing exactly where, right down to exact line numbers, an exception was thrown (EDIT: and the code path which led up to the exception) is amazingly useful (I really don't think I can overstate how useful it is). Returning error codes, in this regard, aren't nearly as useful.

    I wonder if it would be possible to achieve a similar effect in Rust by statically analyzing all the points where potential errors are constructed.and setting breakpoints there.



  • @Gaska said:

    And now you have a really nasty dependency hell - the quality of your documentation strongly depends on quality of std::string::operator+ documentation, and every other function you've explicitly or implicitly used in your code.

    One way to solve this is by designing your functions to only throw their own exceptions rather than letting any internal exceptions escape. This would especially be true of public-facing API functions - it would be far more useful to have an API function throw its own IndexOutOfRangeException with information about user-supplied data, for instance, than it would be to allow an internal IndexOutOfRangeException generated by a collection to propagate.

    @Gaska said:

    Your application doesn't go to hell by default.

    Not strictly true, of course. It's entirely possible for an application to end up in a semi-fucked state as a result of a failed operation and unhandled error code.

    @Gaska said:

    Thankfully, usually you care only about two cases - no error and any error.

    I very frequently care about what the error was. Was there a null reference? Is an index out of range? Did the user forget to enter a password? FILE_NOT_FOUND? Information like that is important.


  • Java Dev

    @swayde said:

    http://stackoverflow.com/a/7067973

    That doesn't even work correctly. Even if your local C code does not access the stack (not guaranteed) printf() definitely will. And it'll crash then.

    Checking stack overflow is probably going to involve either inline assembly, or setting up for a stack overflow to cause SIGSEGV (or windows equivalent) and trapping that. And if the action upon detecting a stack overflow includes any diagnostics, you'll probably be switching stacks.



  • @Gaska said:

    3) there is an error and you need to handle it somehow - C++ way is catching the exception in some random place in code, possibly multiple places; Rust way is checking the function result, often in the same expression where the error originated from (much easier to follow when reading code)

    Can you not see how you are not comparing equivalent scenarios here? If you have the information to do something about an error return, you have the exact same information to handle the exception there. If you don't have the information to handle the error, you have to pass the error to your caller, and handle the error "in some random place in code, possibly multiple places".

    You can't compare trivial error return scenarios against complex exception scenarios. Trivial errors are trivial to recover from. Complex error are complex to recover from. This doesn't change whether you use error returns or exceptions to signal the error.

    Throwing exceptions vs returning error codes is an error signaling mechanism. Checking the error code or catching the exception is an error handling mechanism. The error handling strategy is independent of the error signaling. You use different constructs to determine what failed, but you resolve the error in the same manner in either case.

    For example, with error codes, you check against the errors you know how to handle, and if the error is outside of those, you decide what to do, most likely return it to your caller (and what happens when you return an unknown error back to main? Your program crashes), or if you hate your users, ignore it.

    With exceptions, you'd have the same behavior. You have all your catch clauses for the errors you know how to handle, and you either let the other errors flow up out of you (and reach main and crash, same as returning an unhandled error code to main), or you swallow it and continue.

    The difference is, you have to actually write code to ignore an error when handling exceptions, while you get to ignore unknown errors for free with return codes. But just because you're not writing code, doesn't mean you're not making a decision.

    The main advantage of exceptions is that it automatically handles error signaling and gets it to your error handling code.


  • Banned

    @Masaaki_Hosoi said:

    One way to solve this is by designing your functions to only throw their own exceptions rather than letting any internal exceptions escape.

    And how do you do it? By wrapping every single function call in try/catch(...)?

    @Masaaki_Hosoi said:

    Was there a null reference? Is an index out of range?

    It's a programmer's error and the application should explode in their face. Catching it is like catching syntax error.

    @Masaaki_Hosoi said:

    Did the user forget to enter a password?

    Honestly, if you handle it via exceptions and not regular business logic, I don't want to ever see your code.

    @Masaaki_Hosoi said:

    FILE_NOT_FOUND?

    An example where catching and handling exception is appropriate. Except that in 99% of cases, the exact reason why you couldn't access the file (I can think of at least a dozen off the top of my head) is irrelevant, and handling the situation doesn't change (you just don't open the damn file).



  • @Gaska said:

    foo(try!(bar())). Or foo(bar().unwrap()). Or foo(bar().or(0)). Or foo(bar().or_else(|| baz()). Or, hell, foo(match bar() { Ok(o) => o, Err(e) => what the fuck you want }) if you're such a special snowflake.
    None of which do anything to help you if you don't actually want to call foo. Which, at least IME, I'd guess is the common case. Then you're back to a full-blown if. (Of course, the opposite is actually true in C++: because there's no try/catch expression, if you do want to call foo with some default value, then it becomes rather worse. But again, common case.)

    Responding a little to the quote before that as well:

    In fact, I often have something where I am in a loop or something carrying out several operations on each element, and if any of them fails I just want to skip a large part of the processing, perhaps all. With exceptions, that turns into

     try {
        thing1();
        thing2(thing3());
        thing4();
    }
    catch (Problem1 const & e) { ... }
    catch (Problem2 const & e) { ... }
    

    which to me is much more clear than

    if(!thing1())
        goto error1;
    int ret;
    if(!thing3(&ret))
        goto error1;
    if(!thing2(ret))
        goto error2;
    if(!thing4())
        goto error2;
    continue;
    error1:
    ...
    error2:
    ...
    

    or something like that. That's not "slightly worse"; IMO, that's much worse.

    @Gaska said:

    initializing a variable to the logical return value of a function

    See above.

    Again, that doesn't help you if you don't have something suitable to put in the error case! You're stuck propagating the either type around. Which brings us in part to this as well..

    @Gaska said:

    You would have the same problem if you threw around integers. In Rust, you don't have error codes but errors - as in, whole objects that can be anything you want. Result is nothing more than tagged union of two arbitrary types.
    And Rust narrows the gap quite a bit. And it's relevant for a return codes/exceptions discussion in general. But I'm also interested in the particular case of C vs C++ because that's what the article was about, and in that language IMO it's no contest between the two.



  • @Gaska said:

    And how do you do it? By wrapping every single function call in try/catch(...)?

    Have you programmed in java? You basically have to, else you have to bubble 200 kinds of exceptions to the ui/user. That gets annoying as fuck. (in java you don't really bubble - you HAVE to catch exceptions)
    http://www.journaldev.com/1696/java-exception-handling-tutorial-with-examples-and-best-practices



  • @Gaska said:

    @Masaaki_Hosoi said:
    One way to solve this is by designing your functions to only throw their own exceptions rather than letting any internal exceptions escape.

    And how do you do it? By wrapping every single function call in try/catch(...)?

    Usually, with simple defensive programming. Check for null objects, ensure values are within expected range, etc. And, yes, if you call a function which could throw exceptions, you check for exceptions. Nobody wants to see a stacktrace which points to some internal API file, particularly if all they have is a DLL.

    @Gaska said:

    @Masaaki_Hosoi said:
    Did the user forget to enter a password?

    Honestly, if you handle it via exceptions and not regular business logic, I don't want to ever see your code.

    To be fair, that one was inspired by a real API I used which did error codes, and there were a multitude of error codes for everything from no username, invalid email, no password, etc. So, apologies for being a little unclear there. But the point of that was that I do care exactly what happened, not just that something happened (in that case, good practice aside, it doesn't matter whether the system uses exceptions or error codes)

    @Gaska said:

    @Masaaki_Hosoi said:
    FILE_NOT_FOUND?

    An example where catching and handling exception is appropriate. Except that in 99% of cases, the exact reason why you couldn't access the file (I can think of at least a dozen off the top of my head) is irrelevant, and handling the situation doesn't change (you just don't open the damn file).

    Yeah, but it's still helpful to distinguish IO errors such as file open failures from other kinds of errors. Maybe I don't need to know why a file didn't open (unless I find myself needing a more specific error message I suppose), but I do need to know whether it was that the file didn't open, or whether it was that an object reference was null, just as an example.

    EDIT: And please understand, I'm not saying error codes are terrible and nobody should use them (I agree there are situations where using an exception for some error conditions would be a :wtf:, such as login failures). I'm just saying that exceptions aren't nearly as bad as you seem to think they are.


  • Banned

    @Kian said:

    Can you not see how you are not comparing equivalent scenarios here? If you have the information to do something about an error return, you have the exact same information to handle the exception there. If you don't have the information to handle the error, you have to pass the error to your caller, and handle the error "in some random place in code, possibly multiple places".

    The difference is, the error code will always be checked at or immediately after the call site, or caller's call site, or caller's caller's call site, and so on. Exception will be caught in zero or more catch blocks located in any of the functions in the stack trace, and thanks to inheritance, you can't find all those places with a simple grep.

    @Kian said:

    You can't compare trivial error return scenarios against complex exception scenarios. Trivial errors are trivial to recover from. Complex error are complex to recover from. This doesn't change whether you use error returns or exceptions to signal the error.

    How does it relate to anything I've said?

    @Kian said:

    Throwing exceptions vs returning error codes is an error signaling mechanism. Checking the error code or catching the exception is an error handling mechanism. The error handling strategy is independent of the error signaling.

    Good luck catching error code.

    @Kian said:

    You use different constructs to determine what failed, but you resolve the error in the same manner in either case.

    Yes, I know you can write code that does the same thing in both Erlang and ActionScript.

    @Kian said:

    and what happens when you return an unknown error back to main? Your program crashes

    Usually, errors don't go that far high, because they need to explicitly move the error higher in call stack. And main() won't crash automatically when error reaches it - it needs to manually cause sigsegv or sigabrt for it.

    @Kian said:

    or if you hate your users, ignore it.

    I don't think MOHA guys hate me that much if they let me play their game before I installed it.

    @Kian said:

    With exceptions, you'd have the same behavior.

    Except half-implicit and less organized. Yes, I don't like implicit stuff. One of the things that sold me on Rust was that u16->u32 conversion requires explicit cast.

    @Kian said:

    The difference is, you have to actually write code to ignore an error when handling exceptions, while you get to ignore unknown errors for free with return codes.

    s/when handling exceptions/in Rust/ s/with return codes/in C/ and the sentence is still true. Unless you don't care about compiler warnings, but then you're surely a shitty programmer and your error handling routine won't work anyway.


  • Banned

    @EvanED said:

    None of which do anything to help you if you don't actually want to call foo.

    The first one does, if you didn't get it.

    @EvanED said:

    In fact, I often have something where I am in a loop or something carrying out several operations on each element, and if any of them fails I just want to skip a large part of the processing, perhaps all.

    try!(thing1()
    .and_then(|| thing2())
    .and_then(|| thing3())
    .and_then(|| thing4()))
    

    @EvanED said:

    Again, that doesn't help you if you don't have something suitable to put in the error case!

    Pardonne moi? What do you mean by suitable? If there's an error, I put an error. And the other guy assigns the Result<A, B> to variable of type A, and it works because he wrapped my function in try!() macro which extracts the A value or returns error (as in, makes the caller return the error he got from callee). I don't get where you see a problem here.

    @swayde said:

    Have you programmed in java?

    I did. A little. Despised it.

    @swayde said:

    You basically have to, else you have to bubble 200 kinds of exceptions to the ui/user. That gets annoying as fuck.

    At least you know what exceptions you need to catch.

    @swayde said:

    in java you don't really bubble - you HAVE to catch exceptions

    Unless you declare your function as throwing the exceptions you didn't bother to catch - then it just sends the exception to the orbit.

    @Masaaki_Hosoi said:

    Usually, with simple defensive programming. Check for null objects, ensure values are within expected range, etc. And, yes, if you call a function which could throw exceptions, you check for exceptions. Nobody wants to see a stacktrace which points to some internal API file, particularly if all they have is a DLL.

    You forgot that in C++, almost any function, including those implicitly called, can throw exception of any type, including types you have no knowledge about (they're not declared in any of headers you include).

    @Masaaki_Hosoi said:

    But the point of that was that I do care exactly what happened, not just that something happened (in that case, good practice aside, it doesn't matter whether the system uses exceptions or error codes)

    And my point was, while you certainly encounter situations like that in life, they're the minority case. Hence word "usually" in my original statement.

    @Masaaki_Hosoi said:

    Yeah, but it's still helpful to distinguish IO errors such as file open failures from other kinds of errors.

    What other errors than file open failure can happen in open_file() function?

    @Masaaki_Hosoi said:

    I'm just saying that exceptions aren't nearly as bad as you seem to think they are.

    In my eyes, they're inferior in every possible way to error returning except for not allowing the program to continue in erroneous state, and auto-breakpointing on error. The cons (mainly the free form of catch blocks) outweigh those pros for me, though. Note that for error-returning being done right, tagged unions are strict requirement.

    Yes, I am Rust fanboy a little. But only because it just happens to implement all the things I wished for in C++, and has some really kickass features.



  • @Gaska said:

    Unless you declare your function as throwing the exceptions you didn't bother to catch - then it just sends the exception to the orbit.

    What ?
    Then you just have to catch them next level up, or in main() AND manually propagate them all the way. If you do not catch them it's an compliation error. That's if you do not convert them to unchecked exceptions, which is kind of subverting the language.



  • @Gaska said:

    The first one does, if you didn't get it.

    @Gaska said:

    try!(thing1()
    .and_then(|| thing2())
    .and_then(|| thing3())
    .and_then(|| thing4()))

    OK, I think I have to back off of my assertions about Rust here. I'm not totally convinced of this, but it looks like it probably gives you tools to build something at least competitive with exceptions. (Though I'll note that you didn't pass the return value of thing3() to thing2().) It's clear I'm not familiar enough with what you can do with Rust to comment intelligently on it.


  • Banned

    @swayde said:

    What ?
    Then you just have to catch them next level up, or in main() AND manually propagate them all the way. If you do not catch them it's an compliation error. That's if you do not convert them to unchecked exceptions, which is kind of subverting the language.

    Give me one example of checked exception you DON'T want to catch at some point.

    @EvanED said:

    Though I'll note that you didn't pass the return value of thing3() to thing2().

    Oh, sorry, didn't read your source carefully enough. It's more like this:

    let ret = try!(thing1());
    try!(thing3(&ret).and_then(|| thing2(ret).and_then(|| thing4()));
    

    It gets a little messy with more complicated cases, yes. Also, all the expressions in try!() macros need to have same error type as your function, or your error type needs to implement From<T> trait for all error types you use in try!(). And all your cleanup must be confined in RAII.



  • @Gaska said:

    let ret = try!(thing1());
    try!(thing3(&ret).and_then(|| thing2(ret).and_then(|| thing4()));

    I suspect you may well be able to do better, because the point of the &ret1 parameter was because C can't do the either return. Can you work that in so that thing3() takes no parameters and just returns it? Recall the exceptiony way of doing it:

    thing1();
    thing2(thing3());
    thing4();
    

    where I want it to dump out at the first failed call.


Log in to reply