C vs C++, from the author of ZeroMQ



  • @Gaska said:

    The difference is, the error code will always be checked at or immediately after the call site,

    Not if the person coding it doesn't check it. Which is the whole point I was making before. Doing the right thing with error codes is a) optional, and b) more work, than doing the same with exceptions.

    @Gaska said:

    or caller's call site, or caller's caller's call site, and so on.
    So the error code bubbles up the stack, same as exceptions, until it is handled. What does if (result != SUCCESS) return result; add in terms of readability?

    @Gaska said:

    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.
    I don't understand what you are trying to say here. Doing a search for catch(ExceptionType seems like it will tell you exactly where your exception is going to be caught. Or you can do a search for the error value if you defined ExceptionType as having an enum value. And how is searching for an error type any better?

    @Gaska said:

    How does it relate to anything I've said?
    You compared
    @Gaska said:
    Rust way is checking the function result, often in the same expression where the error originated from
    with
    @Gaska said:
    C++ way is catching the exception in some random place in code, possibly multiple places
    If you can handle the error inline, that's a trivial error. If you have to throw an error and catch it further down the stack, that's a more complex error. They are not equivalent scenarios.

    Maybe I misunderstood. If you meant that in a single function, you'd catch each error and handle it inline, but with exceptions you have to wrap a block in try and catch every possible error in a separate catch clause, I'd still say exceptions win out though. And the "multiple places" bit still doesn't make sense. Yes, the error handling code isn't next to the function that failed. That's a good thing. You're thinking in the specific case where you just have one call. Consider if you have several calls that could fail. With error codes, you have to have an error handling block after each call, which distracts you when you are trying to follow the good path, and might create a more complex structure as different error handling routines might interact (for example, maybe you have to pass the first error to your caller, but you can deal with the second error inline. With exceptions, you just have a catch clause for the second error, with error codes you have to have an if and early return and then a block of code).

    Exceptions let you clearly write what you intend for the code to do, and deal with what happens when what you intended fails in a separate block of code. Yes, the try{}catch(){} syntax is not perfect, but its a lot clearer than sticking macros everywhere, and goto error1; and the like that happens with error codes.

    @Gaska said:

    Good luck catching error code.
    Reading fail? Ah, I was not clear enough. I meant to say "independent of the error signaling and handling mechanism". Notice the different wording between error handling mechanism and error handling strategy. The mechanism is how you do it. The strategy is what you do with it.

    @Gaska said:

    Usually, errors don't go that far high, because they need to explicitly move the error higher in call stack.
    Exactly, and someone will eventually forget to check for an error or will just ignore an unknown error, right? Errors don't just disappear because you didn't handle them. You either handle errors, or pass them back to your caller. What magical mechanism do you use to prevent errors from reaching main, if you don't know how to handle them?

    An unhandled error will cause your program to exit with an error code, just as an unhandled exception will, unless you forget to pass the unhandled error up to your caller, in which case your program is now in an undefined state and could potentially be destroying your user's data.

    @Gaska said:

    And main() won't crash automatically when error reaches it - it needs to manually cause sigsegv or sigabrt for it.
    Right, my bad. I've been using "crashes" to mean "exits abnormally". But then, preventing a crash from an unhandled exception is trivial. Just have a catch all clause in main so you exit cleanly instead of crashing. It takes all of twenty characters. Compared to all the error checking you would have to do everywhere else all the time with error codes, it's still a win.

    @Gaska said:

    I don't think MOHA guys hate me that much if they let me play their game before I installed it.
    Ignoring something is not the same as choosing not to act on it. There was probably quite a bit of code put in place to prevent missing resources from crashing the game. This is not the same as ignoring errors.

    @Gaska said:

    Yes, I don't like implicit stuff.
    Except for implicitly ignoring unknown error codes. That you are happy with.

    @Gaska said:

    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.
    Congrats, Rust got you to the same point that exceptions already are at, except you have to work for it.

    @Gaska said:

    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).
    Within any given function, your job is not to catch and handle every possible exception. Your job is to handle the exceptions you know how to handle, ignore everything else, and stick to your exception safety specification. It's the same as with error codes. What do you gain from knowing what error codes might be returned, if you don't actually handle them at that particular point?

    Exception safety means writing your code such that even when any function you called could throw, you don't leak resources and your objects are in a valid state. It's always possible to provide that guarantee at no performance cost, and it makes your code more fault tolerant whether you use error codes or exceptions. You shouldn't be afraid of exceptions if you properly use RAII.

    @Gaska said:

    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.
    Is better performance not a "possible way" for you? Error checking requires error checking, which means an if after every function and a comparison. Exception based code takes success for granted, since there's a side channel for errors to take, and so does away with all that in the success path. It's a trade-off between predictable slowness all around and unpredictable slowness when there's an error, but since there should be fewer errors than successes, it seems like the right trade-off to make.



  • @Kian said:

    Doing a search for catch(ExceptionType seems like it will tell you exactly where your exception is going to be caught.
    That will get no hits, because I said catch(ParentExceptionType instead. That's what Gaska means, and the ability to do that is both powerful (and probably "necessary" for a reasonable exception system) and disruptive for that and other reasons.



  • Oh, right. I see how that can be a problem, sort of. Not really a deal breaker for me.


  • Banned

    @EvanED said:

    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:

    try!(thing1().and_then(|| thing2(try!(thing3()))).and_then(|| thing4()))
    On a "clean" path, try!() macro is just extracting the Ok value from Result type - same as unwrap() (which, you know, unwraps the value), but instead of panicking, it makes caller return error if there is one. and_then() takes a no-argument function as argument and calls it only if the original result is Ok. Its counterpart is or_else(), which calls the provided function only if the original result is Err. Oh, and || is lambda declaration (normally, between the pipes are arguments). It's quite simple once you get to know those functions.

    @Kian said:

    Not if the person coding it doesn't check it. Which is the whole point I was making before. Doing the right thing with error codes is a) optional, and b) more work, than doing the same with exceptions.

    Again, it's a language problem. Rust enforces error checks quite nicely.

    @Kian said:

    So the error code bubbles up the stack, same as exceptions, until it is handled.

    Yes; how else would you imagine error handling!?

    @Kian said:

    What does if (result != SUCCESS) return result; add in terms of readability?

    Strawmanning with C aside, it helps in two ways:

    1. it took you seconds to arrive here because you knew exactly where to look for error handling,
    2. it will take you seconds to arrive at your next stop because you know exactly where to look.

    @Kian said:

    I don't understand what you are trying to say here. Doing a search for catch(ExceptionType seems like it will tell you exactly where your exception is going to be caught.

    Let's say you have FooException deriving BarException deriving std::exception. You need to grep for catch(FooException, catch(BarException, catch(std::exception, aaand, obviously, catch(...). And they can be anywhere between the call site and function's closing brace, at every level of call stack. And some of them won't be waiting for exception from your function. And some of the ones waiting for your exception will handle it and throw again - usually the original exception, but sometimes a new one.

    @Kian said:

    If you can handle the error inline, that's a trivial error. If you have to throw an error and catch it further down the stack, that's a more complex error. They are not equivalent scenarios.

    I wasn't speaking of error handling, I was speaking of error checking. As in, where the error handling starts, so you can analyze what happens then. The actual error handling, ie. reacting to bad stuff, can be arbitrarily long in both cases. But control flow is much simpler with returns.

    @Kian said:

    And the "multiple places" bit still doesn't make sense.

    I meant rethrowing within catch block.

    @Kian said:

    Consider if you have several calls that could fail. With error codes, you have to have an error handling block after each call, which distracts you when you are trying to follow the good path

    I have a feeling you ignore every post that isn't specifically directed to you... Do those Rust snippets I've posted above look that bad to you?

    @Kian said:

    Yes, the try{}catch(){} syntax is not perfect, but its a lot clearer than sticking macros everywhere, and goto error1; and the like that happens with error codes.

    So, goto is fine if it's not called goto? Because try/catch is just goto in disguise. I don't like goto not because it's considered evil, but because it can shift the control flow anywhere.

    @Kian said:

    Exactly, and someone will eventually forget to check for an error or will just ignore an unknown error, right?

    Forget? Possibly - though in Rust the risk is close to zero, thanks to compiler warning. Ignoring unknown error? Doubt so - if someone has already bothered to write if(){}else if(){}, I can't see how could he not also write else() for every error he doesn't care about.

    @Kian said:

    Right, my bad. I've been using "crashes" to mean "exits abnormally".

    It won't exit abnormally in any other way either if you don't explicitly tell it to.

    @Kian said:

    But then, preventing a crash from an unhandled exception is trivial. Just have a catch all clause in main so you exit cleanly instead of crashing. It takes all of twenty characters.

    I seriously hope you are sarcastic here.

    @Kian said:

    Compared to all the error checking you would have to do everywhere else all the time with error codes, it's still a win.

    Remember, time spent on programming is 90% thinking and 10% coding (70/30 in Java).

    @Kian said:

    Ignoring something is not the same as choosing not to act on it. There was probably quite a bit of code put in place to prevent missing resources from crashing the game. This is not the same as ignoring errors.

    Actually, I think it goes more like this: the texture file is missing, so it doesn't open, so 0 bytes are read, so 0-sized texture is created, so it's all black because that's the default border color for OpenGL textures. This way of doing things is what I call ignoring.

    @Kian said:

    Except for implicitly ignoring unknown error codes. That you are happy with.

    Come again? How does running generic error handler mean ignoring it? What's implicit in here?

    @Kian said:

    Congrats, Rust got you to the same point that exceptions already are at, except you have to work for itbetter.

    FTFY.

    @Kian said:

    Within any given function, your job is not to catch and handle every possible exception. Your job is to handle the exceptions you know how to handle, ignore everything else, and stick to your exception safety specification. It's the same as with error codes. What do you gain from knowing what error codes might be returned, if you don't actually handle them at that particular point?

    You can, for example, document all the possible failure modes for your function, which would be very, very useful for anyone wanting to use it?

    @Kian said:

    Is better performance not a "possible way" for you?

    Exceptions hurt performance. At least in C++. Did you know that with exceptions enabled, the compiler needs to put extra boilerplate in each function's prologue? And that stack unwinding due to exception is quite more computationally difficult than comparison of return value against zero?



  • @Gaska said:

    @Kian said:
    Ignoring something is not the same as choosing not to act on it. There was probably quite a bit of code put in place to prevent missing resources from crashing the game. This is not the same as ignoring errors.

    Actually, I think it goes more like this: the texture file is missing, so it doesn't open, so 0 bytes are read, so 0-sized texture is created, so it's all black because that's the default border color for OpenGL textures. This way of doing things is what I call ignoring.

    I find that highly unlikely. Any sane game engine would check that the resource is null before attempting to upload it to the graphics card. So most likely the blackness is that game's version of a placeholder texture.

    @Gaska said:

    @Kian said:
    Within any given function, your job is not to catch and handle every possible exception. Your job is to handle the exceptions you know how to handle, ignore everything else, and stick to your exception safety specification. It's the same as with error codes. What do you gain from knowing what error codes might be returned, if you don't actually handle them at that particular point?

    You can, for example, document all the possible failure modes for your function, which would be very, very useful for anyone wanting to use it?

    Which you can't possibly do with exceptions?

    @Gaska said:

    Exceptions hurt performance.

    This is true, although the performance hit is still relatively small.



  • @Gaska said:

    Strawmanning with C aside, it helps in two ways:

    Strawmanning with C...? Ok, we're apparently having two different conversations. I was arguing against error codes in general, but in particular the C implementation. You know, the thing that the links in the OP discuss. I now realize you're arguing about error returns in Rust specifically. Which I can't really talk much about.

    @Gaska said:

    1) it took you seconds to arrive here because you knew exactly where to look for error handling,
    Great, I arrived at something that tells me "Sorry, I don't actually do anything, I just pass the error up!". At least it's fast, so I don't waste so much of my time.

    @Gaska said:

    2) it will take you seconds to arrive at your next stop because you know exactly where to look.
    What if someone does something like:

    ANY_ERROR = ERROR1 | ERROR2;
    

    And then they use ANY_ERROR to handle instances of ERROR1 or ERROR2? That's the equivalent problem to using inheritance in exceptions. And it's the same problem. Now if you want to see where ERROR1 is handled, you need to look at all instances of ERROR1 and all instances of ANY_ERROR. Just as with exceptions, you would need to look for the exception itself, and all the parent classes. And at least inheritance creates structure. A determined idiot could combine unrelated error types (cast to int and OR the ints) and make your life a lot harder.

    @Gaska said:

    Let's say you have FooException deriving BarException deriving std::exception
    As I pointed out above, please compare equivalent issues. You can create for yourself the same problem with error codes. If hitting your finger with a hammer hurts, stop doing it.

    @Gaska said:

    I wasn't speaking of error handling, I was speaking of error checking.
    Ok, you'll have to explain to me what error checking is supposed to accomplish. As far as I understand it, if you have an error, you either chuck it to your caller, or you handle it yourself. With exceptions, passing it along is automatic. If there's anything you could potentially do yourself, you have a catch clause. With error codes, as far as I understood, you either return it or you handle it, same as with exceptions. What else would error checking do?

    @Gaska said:

    I meant rethrowing within catch block.
    You realize you can do the same with error codes? You detect an error, perform some action, and then return the same error code you received. If you can fix an error without returning the error to your caller, you can also handle the exception without rethrowing. Please use equivalent scenarios when comparing things.

    @Gaska said:

    I have a feeling you ignore every post that isn't specifically directed to you... Do those Rust snippets I've posted above look that bad to you?
    I kind of missed some stuff while posting. Although I noticed that the complexity of the code grew as you tried to account for more complex relationships in the original code. The exception based code certainly looked a lot cleaner.

    @Gaska said:

    So, goto is fine if it's not called goto? Because try/catch is just goto in disguise. I don't like goto not because it's considered evil, but because it can shift the control flow anywhere.
    You realize a function call is just a goto in disguise too? And it can shift your control flow anywhere? Do you avoid function calls too?

    @Gaska said:

    Forget? Possibly - though in Rust the risk is close to zero, thanks to compiler warning.
    You keep bringing this up as if it was a give. It isn't. I've worked at places where warnings were ignored. Compiling would throw thousands of warnings. I'd clean up any warnings I found in files I touched, then see the warning count climb because everyone else didn't care. So I'm not a confident as you that everyone will do the right thing. I'd wager more people will just do whatever shuts the compiler up the fastest, assuming they care at all beyond "it compiles", before doing the right thing.

    @Gaska said:

    It won't exit abnormally in any other way either if you don't explicitly tell it to.
    "Exits abnormally" would be exits and returns something other than zero. I imagine if an unhandled error reaches main, you won't exit main with an EXIT_SUCCESS.

    @Gaska said:

    I seriously hope you are sarcastic here.
    What exactly do you think is wrong about it? You receive an unhandled error you don't know how to handle in main. If you don't handle it, your program calls terminate. If you handle it, your stack unwinds cleanly, you close file handles, flush stuff to disk, close network connections, etc, and you just exit with whatever return code you want to give. I wouldn't do it in the debug build, since I want to see the state of the program when things went wrong, but on a release build? Sure. Can you describe what's wrong about it?

    @Gaska said:

    Actually, I think it goes more like this: the texture file is missing, so it doesn't open, so 0 bytes are read, so 0-sized texture is created, so it's all black because that's the default border color for OpenGL textures. This way of doing things is what I call ignoring.
    What would actually happen is, the texture file is missing, so fopen returns a null FILE pointer. I then try to read from the null FILE pointer, and my program crashes. It takes more programming to create a null object that I can pass around and won't kill my program, and creating the null object in the first place is a design decision to not react to problems that are not immediately fatal. Not a decision to ignore them.

    @Gaska said:

    Come again? How does running generic error handler mean ignoring it? What's implicit in here?
    I was still under the impression that we were talking about the OP.

    @Gaska said:

    You can, for example, document all the possible failure modes for your function, which would be very, very useful for anyone wanting to use it?
    Really? What if your function calls a library, and that library is then updated to return new error codes? What if you call code that hasn't been as thoroughly documented? The belief that you can know every way in which some piece of code could fail is wrong. If you are only prepared to handle the errors you know about, your code is not resilient. You have to be able to handle the errors you don't know about, and because you have to be able to do that, knowing every possible error is not always useful. It just adds noise. Documentation is good, but perfect knowledge doesn't exist, and for most people it isn't useful.

    What is useful is knowing what exception guarantees your code offers. The basic, strong, or no throw guarantees. And you don't need to know how to react to every single failure mode to make use of that.

    But, in any case, there is no reason why you should have more information about error codes than about exceptions. Again, exceptions are not magic, they don't spring fully formed from nothingness. If you can provide a description of all the error codes you return, you can provide a description of all the exceptions you might throw.

    @Gaska said:

    Exceptions hurt performance. At least in C++. Did you know that with exceptions enabled, the compiler needs to put extra boilerplate in each function's prologue?
    This is at best outdated. Read this.

    @Gaska said:

    And that stack unwinding due to exception is quite more computationally difficult than comparison of return value against zero?
    Yes, this is what I said when I said that it's a trade off where the successful path runs faster at the cost of the failure path running slower.


  • Winner of the 2016 Presidential Election

    @Gaska said:

    I meant rethrowing within catch block.

    That's not "catching in multiple places". Because you should never catch and re-throw an exception unless you're wrapping it in another, higher-level exception to make errors easier to handle for the user of your API. Also, if you use error codes, you'd have to write error handling code in multiple places as well if you want to achieve the same thing.

    @Gaska said:

    Because try/catch isfor loops are just goto in disguise.

    FTFY. Seriously, that's not an argument. Also, it's wrong, since:

    1. goto jumps to a specific place in your code instead of wherever the user of your code chooses to handle the error
    2. goto doesn't. give you a stack trace

    @Gaska said:

    I seriously hope you are sarcastic here.

    ???

    catch (std::exception& e) in your main method or at the boundary to public API is completely fine. Everywhere else it's wrong.

    @Gaska said:

    Exceptions hurt performance. At least in C++.

    I always thought they only hurt performance in case they're actually thrown, which isn't exactly the case you should optimize for, and actually benefit performance in case they're not thrown because you need less branches.


  • Discourse touched me in a no-no place

    @asdf said:

    Because you should never catch and re-throw an exception unless you're wrapping it in another, higher-level exception to make errors easier to handle for the user of your API.

    What, never? Well, hardly ever.

    I am the Captain of the Pinafore - HMS Pinafore – [00:58..02:15] 02:15
    — Stay On Target

    There are cases where it is necessary, such as when you want to handle complement sets of errors (e.g., all I/O errors except a failure to open a file due to it being absent).


  • Banned

    @Masaaki_Hosoi said:

    I find that highly unlikely. Any sane game engine would check that the resource is null before attempting to upload it to the graphics card. So most likely the blackness is that game's version of a placeholder texture.

    It's not null, it's zero. Also, as I said, black is the default of OpenGL, not that particular game - so they didn't have to put any placeholders of theirs.

    @Masaaki_Hosoi said:

    Which you can't possibly do with exceptions?

    No, you can't, for the reasons I've already mentioned several times.

    @Masaaki_Hosoi said:

    This is true, although the performance hit is still relatively small.

    Even smaller is the performance hit of error checking.


  • Banned

    @Kian said:

    Strawmanning with C...? Ok, we're apparently having two different conversations. I was arguing against error codes in general, but in particular the C implementation. You know, the thing that the links in the OP discuss. I now realize you're arguing about error returns in Rust specifically. Which I can't really talk much about.

    I'm arguing about returning errors in general, as in disregarding the suckiness of C way of doing it because C sucks hard in this matter and it's so obvious there's nothing to discuss.

    @Kian said:

    What if someone does something like:

    ANY_ERROR = ERROR1 | ERROR2;


    Then you track the life of ANY_ERROR instead of ERROR1. Big deal. Note that you won't have to guess what happened to the original error, because you know it must have happened at call site.

    @Kian said:

    And at least inheritance creates structure.

    Inheritance creates structure of hierarchical classification of error types. Error returning creates structure of where the error handling happens.

    @Kian said:

    As I pointed out above, please compare equivalent issues. You can create for yourself the same problem with error codes. If hitting your finger with a hammer hurts, stop doing it.

    So, you're saying that more than one level of exception inheritance is Doing It Wrong? Or is exception inheritance at all Doing It Wrong?

    @Kian said:

    Ok, you'll have to explain to me what error checking is supposed to accomplish. As far as I understand it, if you have an error, you either chuck it to your caller, or you handle it yourself. With exceptions, passing it along is automatic. If there's anything you could potentially do yourself, you have a catch clause. With error codes, as far as I understood, you either return it or you handle it, same as with exceptions. What else would error checking do?

    You've got it a little backwards - error checking is part of error handling, not the other way around. You check for error, then you handle it (or not). Error checking with error codes always happens at call site, and error handling immediately follows. Error checking with exceptions happens implicitly, and God knows where it's handled.

    @Kian said:

    You realize you can do the same with error codes? You detect an error, perform some action, and then return the same error code you received. If you can fix an error without returning the error to your caller, you can also handle the exception without rethrowing. Please use equivalent scenarios when comparing things.

    The difference between returning errors and exceptions is that eventual "rethrow" goes exactly one stack frame up. It will not go into the same stack frame, nor it will go two stack frames up, unless it's due to rethrowing it again. With exceptions, God knows where it ends up.

    @Kian said:

    What exactly do you think is wrong about it? You receive an unhandled error you don't know how to handle in main. If you don't handle it, your program calls terminate. If you handle it, your stack unwinds cleanly, you close file handles, flush stuff to disk, close network connections, etc, and you just exit with whatever return code you want to give.

    There's no way you can meaningfully handle an arbitrary exception in main() - and if you can't meaningfully handle exception, you shouldn't catch it. Stack unwinding happens regardless of if you catch it or not.

    @Kian said:

    I wouldn't do it in the debug build, since I want to see the state of the program when things went wrong

    First chance exception anyone?

    @Kian said:

    Can you describe what's wrong about it?

    Imagine if someone would want to meaningfully handle exceptions in main()...

    @Kian said:

    What would actually happen is, the texture file is missing, so fopen returns a null FILE pointer. I then try to read from the null FILE pointer, and my program crashes. It takes more programming to create a null object that I can pass around and won't kill my program, and creating the null object in the first place is a design decision to not react to problems that are not immediately fatal. Not a decision to ignore them.

    Pseudocode:

    Texture tex; // with zeroing default constructor
    FILE* f = fopen("dupa");
    if (f != NULL) {
        load_data(f, tex);
    }
    return tex;
    

    Not that much effort, considering you have to write that if anyway.

    @Kian said:

    I was still under the impression that we were talking about the OP.

    I didn't really read OP. But I doubt there was a claim that generic error handler is synonymous to ignoring. And even if there was, it doesn't make you any less wrong.

    @Kian said:

    This is at best outdated. Read this.

    And you should read about out-of-order-execution which effectively makes error checks near-free (less than 1 processor cycle on average). In general, many old adages about performance are irrelevant in 2010s.


  • Banned

    @asdf said:

    That's not "catching in multiple places". Because you should never catch and re-throw an exception unless you're wrapping it in another, higher-level exception to make errors easier to handle for the user of your API.

    Probably. But not everyone knows that.

    @asdf said:

    Because for loops are just goto in disguise.

    You should read up more on Edsger Dijkstra's works.



  • @Gaska said:

    Then you track the life of ANY_ERROR instead of ERROR1. Big deal. Note that you won't have to guess what happened to the original error, because you know it must have happened at call site.

    You misunderstood. You define ANY_ERROR to handle errors ERROR1 and ERROR2. Your error code is still one of ERROR1 or ERROR2, not ANY_ERROR. But because ANY_ERROR exists, if you want to know where an error code of ERROR1 is handled, you also have to check for places where ANY_ERROR is handled. So error codes don't make you immune to the problem that you have with inheritance in exceptions.

    @Gaska said:

    Error returning creates structure of where the error handling happens.
    No it doesn't, it just adds noise that you think looks like structure. You just have a huge pile of try!() until you reach a spot where the actual handling happens. With exceptions the try!() is implicit, until you reach a catch. The problem you have with catch and inheritance is not inherent to exceptions, and error codes are not immune to it either.

    @Gaska said:

    So, you're saying that more than one level of exception inheritance is Doing It Wrong? Or is exception inheritance at all Doing It Wrong?
    If it's not making your life easier, it's doing it wrong, yes. Abstractions are meant to make your life easier. If you are not gaining anything in terms of programming ease by creating a hierarchical classification of error types, don't do it. If you are gaining something, then you have to consider the benefits you are obtaining against the costs, such as a harder time searching for where one specific error is handled.

    If the trade-off is worth it, then exceptions have actually netted you a positive. If it isn't, you don't do it and it costs you nothing. The result is that exceptions are either a net positive or equivalent, which makes them strictly better. Obviously people can do things wrong when given options, but I don't believe in refusing people options because some may make mistakes.

    @Gaska said:

    You check for error, then you handle it (or not). Error checking with error codes always happens at call site, and error handling immediately follows(or not).
    FTFY. So error checking is essentially the thing that exceptions do automatically for you. But you think having to type more is better? Do you enjoy pushing values on the stack manually before a function call too? Or do you just let the language take care of it for you?

    @Gaska said:

    God knows where it's handled.
    You can have just as much trouble finding where an error was handled with error codes.

    @Gaska said:

    The difference between returning errors and exceptions is that eventual "rethrow" goes exactly one stack frame up.
    That's objectively wrong. Here's a trivial example:

    enum class Status
    {
      S_OK,
      E_FAIL
    };
    
    Status Function1()
    {
      return E_FAIL;
    }
    
    Status Function2()
    {
      Status ret = Function1();
      if (ret == E_FAIL)
      {
        // Do some error handling
        // The pass the error up
        return ret;
      }
      return S_OK;
    }
    
    Status Function3()
    {
      return Function2();
    }
    

    Notice how the error is generated in Function1, Function2 does some handling and "rethrows", and Function3 doesn't care and lets the error bubble up. For the rethrown error to only go one stack frame up, the function below has to catch it and handle it, but they have no obligation to do it. Just as with exception, the stack frame up can either catch the exception or let it bubble up.

    For completeness, here's an example with exceptions that behaves identically.

    class MyException : public std::exception
    {
      public:
        static const char* what() override { return "Something failed";}
    }
    
    void Function1()
    {
      throw MyException();
    }
    
    void Function2()
    {
      try
      {
        Function1();
      }
      catch(MyException& e)
      {
        // Do some error handling
        // The pass the error up
        throw e;
      }
    }
    
    void Function3()
    {
      Function2();
    }
    

    Just as before, the exception is generated in Function1, Function2 catches it, does some handling, and rethrows, and Function3 doesn't care.

    @Gaska said:

    There's no way you can meaningfully handle an arbitrary exception in main() - and if you can't meaningfully handle exception, you shouldn't catch it. Stack unwinding happens regardless of if you catch it or not.
    No it doesn't. Please read this. The important bit is the first point there:
    std::terminate() is called by the C++ runtime when exception handling fails for any of the following reasons:

    1. an exception is thrown and not caught (it is implementation-defined whether any stack unwinding is done in this case)

    I can't speak for other languages, but in C++, if an exception reaches main and you don't catch it there, terminate gets called. The implementation decides whether to unwind the stack, and as this stackoverflow question shows, at least some implementations choose not to. So a catch all clause in main is a simple way to ensure that no matter what happens, your program will exit cleanly.

    @Gaska said:

    Imagine if someone would want to meaningfully handle exceptions in main()...

    There's nothing stopping them from adding any meaningful handlers. Look:

    int main()
    {
      try
      {
        // Your program goes here
        return 0;
      }
      catch(MeaningfulException1& e1) { /*do something meaningful*/ return 1;}
      catch(MeaningfulException2& e2) { /*do some other thing*/ return 2; }
      catch(...) { /* Catch anything else that we don't know how to handle so we have a clean exit */ return -1; }
    }
    

    Exceptions that reach main will check against the handlers, top to bottom, and be handled by whichever matches them. So if MeaningfulException1 inherits from MeaningfulException2, the first handler will catch any instances of MeaningfulException1. The catch all clause just makes sure that even if the implementation doesn't unwind before calling terminate, we will.

    I'm getting the impression that although you don't like exceptions, you don't actually have all that much experience with them. Have you worked professionally with them before? I know C++ does things differently (better) than Java for example, so maybe you're thinking of some other, lesser language's exceptions. I can't speak for how other languages implement things.

    @Gaska said:

    Not that much effort, considering you have to write that if anyway.
    I didn't say it took much effort. I said it was a design decision. Some decisions are easy. Here, you are not ignoring the error. You are checking for the error, and handling it by creating a null object. Null objects are a way of avoiding error handling in the first place, by saying "this is now not an error". Which is unrelated to the "error code vs exception" argument.

    @Gaska said:

    And you should read about out-of-order-execution which effectively makes error checks near-free (less than 1 processor cycle on average). In general, many old adages about performance are irrelevant in 2010s.
    Yes, it's less relevant today. But still, "less than 1 processor cycle on average" is more than zero processor cycles when things are going well (which is the case we generally optimize for). If I have to show a popup and wait for the user to make a decision (a common response to certain kinds of errors), performance doesn't matter.



  • @Gaska said:

    @Masaaki_Hosoi said:
    I find that highly unlikely. Any sane game engine would check that the resource is null before attempting to upload it to the graphics card. So most likely the blackness is that game's version of a placeholder texture.

    It's not null, it's zero. Also, as I said, black is the default of OpenGL, not that particular game - so they didn't have to put any placeholders of theirs.

    And you know it's not the game's own error checking for sure because... you've looked at the game's source code? I'll admit, I don't know for certain either but the game doing some basic error checking is far more likely to me than the game trying to upload a resource to the graphics card that doesn't exist.

    As far as that code snippet, it bugs me a little (in particular, the way you declare Texture tex implying it as a struct, which feels wrong to me). But still a little closer to what I mean - defensive programming producing black textures as a placeholder.


  • Banned

    @Kian said:

    You misunderstood. You define ANY_ERROR to handle errors ERROR1 and ERROR2. Your error code is still one of ERROR1 or ERROR2, not ANY_ERROR. But because ANY_ERROR exists, if you want to know where an error code of ERROR1 is handled, you also have to check for places where ANY_ERROR is handled. So error codes don't make you immune to the problem that you have with inheritance in exceptions.

    I don't understand what you mean. Is ANY_ERROR an error handler that gets run on any error? If so, well, that's the fucking point.

    @Kian said:

    No it doesn't, it just adds noise that you think looks like structure. You just have a huge pile of try!() until you reach a spot where the actual handling happens. With exceptions the try!() is implicit, until you reach a catch. The problem you have with catch and inheritance is not inherent to exceptions, and error codes are not immune to it either.

    The problem with catch is that you don't know where this catch is until you find it. This IS inherent to exceptions, and returns don't have such problem AT ALL.

    @Kian said:

    If it's not making your life easier, it's doing it wrong, yes. Abstractions are meant to make your life easier. If you are not gaining anything in terms of programming ease by creating a hierarchical classification of error types, don't do it. If you are gaining something, then you have to consider the benefits you are obtaining against the costs, such as a harder time searching for where one specific error is handled.

    It's not about gaining or not gaining in particular case. It's about what theoretically can be done with the code (and according to Murphy's laws, if it can happen, someone WILL do it).

    @Kian said:

    FTFY

    Exactly. With error codes, error handling immediately follows or not follows. With exceptions, error handling immediately or not immediately follows or not. See the difference?

    @Kian said:

    You can have just as much trouble finding where an error was handled with error codes.

    No you can't. Read any of my posts in this topic for why.

    @Kian said:

    That's objectively wrong. Here's a trivial example:

    Function1 "throws" an error. Function2, which is one stack frame up from Function1, "catches" it and "rethrows". Function 3, which is one stack frame up Function2, "catches" Function2's error and "rethrows it" one level up. The fact it doesn't have equality comparison on the line error is expected is irrelevant.

    @Kian said:

    No it doesn't. Please read this. The important bit is the first point there:

    Oh shit, C++ is even more fucked up that I thought.

    Anyway, usually it's not a problem, because when a process terminates, all resources it had (memory, file handles, etc.) are freed by the operating system - and freeing those is exactly what 99% of destructors do.

    @Kian said:

    There's nothing stopping them from adding any meaningful handlers.

    That's the problem.

    @Kian said:

    I'm getting the impression that although you don't like exceptions, you don't actually have all that much experience with them.

    You're right. I don't have much experience with them. Neither I have much experience with HTML. I never have used JS and PHP in my life. Yet I can talk all day long about everything wrong with any of them. Actually, I can do this with anything, even things I like. Basically, everything even remotely related to IT industry is fundamentally wrong on many levels. But some things are more wrong than others.

    @Kian said:

    Yes, it's less relevant today. But still, "less than 1 processor cycle on average" is more than zero processor cycles when things are going well (which is the case we generally optimize for). If I have to show a popup and wait for the user to make a decision (a common response to certain kinds of errors), performance doesn't matter.

    You know what "less than 1 processor cycle on average" means? It means that the moment you opened a file and read from it, you've just wasted more processor time than you would ever gain from removing all error checks altogether. It's not just less relevant today; it's irrelevant.


  • Banned

    @Masaaki_Hosoi said:

    And you know it's not the game's own error checking for sure because...

    A gut feeling. I know a thing or two about gamedev, and know how some common problems are usually tackle, and what they result in - in this particular case, black textures mean they used a texture despite failing to load it from disk. If they wanted to do something more sophisticated, they would shut down the game with a nice dialog box about missing files. There's no really any other sane way to do it (actual fallback textures aren't sane).

    @Masaaki_Hosoi said:

    As far as that code snippet, it bugs me a little (in particular, the way you declare Texture tex implying it as a struct, which feels wrong to me).

    It was pseudocode. The actual code would involve several ugly OGL calls. But the structure is more or less correct. Also, I think we're not agreeing on what a resource is - for you, it's a file, and for me, it's an already loaded texture.

    Note: all the OpenGL calls related to creating a texture don't need the texture data actually loaded, except for glTexImage2D which uploads texture data to GPU memory.



  • @Gaska said:

    I don't understand what you mean. Is ANY_ERROR an error handler that gets run on any error? If so, well, that's the fucking point.

    You claim that finding where an exception is handled is difficult. I claim it isn't, because you can just search for the catch clause, and that takes you to all the places where that kind of exception is handled. You argued that with inheritance, things become more difficult because now the exception is handled either in it's own handler, or the handler of any of it's parents. Is that an accurate summation?

    My point is that you can engineer the same situation with error codes. You could for example have different error classes, which different functions use to signal errors, and which have different names for similar errors (E_FAIL vs ERROR1 perhaps) and when a function calls another with a different error class, have them convert from one to another. And then to know where an error is handled you have to check for that error code, or any of the error codes it might have been converted to.

    @Gaska said:

    The problem with catch is that you don't know where this catch is until you find it. This IS inherent to exceptions, and returns don't have such problem AT ALL.
    You magically know where the error handler is for your error code before you find it? You see a "return ERROR1;" in a function, and immediately know all of the functions that call that function and how they handle that error? Shit, I didn't know error codes made you one with the universe and granted such insight.

    @Gaska said:

    It's not about gaining or not gaining in particular case. It's about what theoretically can be done with the code (and according to Murphy's laws, if it can happen, someone WILL do it).
    But not with error codes, because everyone always obeys compiler warnings even if they don't have to. Murphy doesn't dare touch error codes, and everyone who uses them always uses them correctly.

    @Gaska said:

    Exactly. With error codes, error handling immediately follows or not follows. With exceptions, error handling immediately or not immediately follows or not. See the difference?

    The hell are you talking about? You seem to be claiming that "all errors are always handled, some exceptions are not handled" (except said in the most obtuse way possible), which is a lie.
    @Gaska said:
    The fact it doesn't have equality comparison on the line error is expected is irrelevant.
    By that definition, exceptions always only bubble one frame too. If the frame above doesn't have a handler for it, it is automatically rethrown one more frame up. Exceptions never skip frames, and they never skip matching handlers, so they always just bubble one frame.

    @Gaska said:

    Anyway, usually it's not a problem, because when a process terminates, all resources it had (memory, file handles, etc.) are freed by the operating system - and freeing those is exactly what 99% of destructors do.
    Yeah, but so what? The question was "what is wrong with a catch all clause in main", not "is one absolutely necessary?" You've simply shown that a catch all clause is 1% better than not having one (since killing a process accomplishes 99% of what stack unwinding does, by your numbers), even if not having one is not strictly necessary. Which is the exact opposite of your initial claim, but you're acting like you were right all along.

    @Gaska said:

    You're right. I don't have much experience with them.
    Well, this wouldn't immediately disqualify you. You could in theory show you actually understand the subject matter despite not having used it. Unfortunately, given that I've had to correct some pretty basic misconceptions of yours, such as the definition of "exception safety" which is pretty central to exceptions in general, you may want to consider the possibility that you may not actually know what you're talking about.



  • I do get your point about the OpenGL border color thing - I wouldn't be surprised if the game uploaded a zero-size texture intentionally, as a placeholder.

    @Gaska said:

    There's no really any other sane way to do it (actual fallback textures aren't sane).

    I suppose Unreal and Source are not sane.


  • Banned

    @Kian said:

    You claim that finding where an exception is handled is difficult. I claim it isn't, because you can just search for the catch clause, and that takes you to all the places where that kind of exception is handled. You argued that with inheritance, things become more difficult because now the exception is handled either in it's own handler, or the handler of any of it's parents. Is that an accurate summation?

    You forgot the fact you might be not the only one using this particular exception class. And that not all catch clauses catch from your function even if type is correct.

    @Kian said:

    My point is that you can engineer the same situation with error codes. You could for example have different error classes, which different functions use to signal errors, and which have different names for similar errors (E_FAIL vs ERROR1 perhaps) and when a function calls another with a different error class, have them convert from one to another. And then to know where an error is handled you have to check for that error code, or any of the error codes it might have been converted to.

    But no matter how much you complicate your example, all you have to do is follow the returns and operations on return values. Error1 won't magically become AnyError if you don't tell it to.

    @Kian said:

    You see a "return ERROR1;" in a function, and immediately know all of the functions that call that function and how they handle that error?

    No, but you can derive all the files and line numbers where error checks happen from list of all files and line numbers where your function is called. Try that with exceptions.

    @Kian said:

    But not with error codes, because everyone always obeys compiler warnings even if they don't have to.

    If you ignore warnings, you have much bigger problems than not-exactly-caught exceptions.

    @Kian said:

    By that definition, exceptions always only bubble one frame too. If the frame above doesn't have a handler for it, it is automatically rethrown one more frame up. Exceptions never skip frames, and they never skip matching handlers, so they always just bubble one frame.

    Implicit rethrows don't count.

    @Kian said:

    Yeah, but so what? The question was "what is wrong with a catch all clause in main", not "is one absolutely necessary?"

    It's a code smell. reinterpret_cast isn't bad per se, but it often indicates something very, very bad is happening.

    @Kian said:

    You've simply shown that a catch all clause is 1% better than not having one (since killing a process accomplishes 99% of what stack unwinding does, by your numbers)

    The remaining 1% of case is when THE EXCEPTION SHOULD ABSOLUTELY BE CAUGHT MUCH MUCH EARLIER THAN MAIN() AND IF IT'S NOT, YOUR WHOLE ARCHITECTURE IS BROKEN ON A FUNDAMENTAL LEVEL. For example, gracefully terminating network connection when the protocol requires more messages to be passed for graceful termination - if you rely on main() catching all uncaught exceptions forcing compiler to trigger correct stack unwinding to do it, then, well...

    @Kian said:

    Unfortunately, given that I've had to correct some pretty basic misconceptions of yours, such as the definition of "exception safety" which is pretty central to exceptions in general

    First, "some" means more than one. Second, I wasn't wrong about exception safety itself but about proper terminology.

    @Kian said:

    you may want to consider the possibility that you may not actually know what you're talking about.

    I understand I might be wrong, but I pretend I'm right so we can have something to argue about. Unless my points will be actually proven wrong - then I'll have nothing to do but admit I was wrong. Which hasn't happened yet.


  • Banned

    @Masaaki_Hosoi said:

    I do get your point about the OpenGL border color thing - I wouldn't be surprised if the game uploaded a zero-size texture intentionally, as a placeholder.

    All textures start as zero-sized, so making a dedicated one seems redundant. A nice thing about OGL is that you don't specify texture size until you upload data.

    @Masaaki_Hosoi said:

    I suppose Unreal and Source are not sane.

    AFAIK Unreal throws an error on missing file. Source might be a little insane, though, considering its goal was to be as nice to modders as possible.



  • @Gaska said:

    AFAIK Unreal throws an error on missing file. Source might be a little insane, though, considering its goal was to be as nice to modders as possible.

    I'm pretty sure Source at the very least logs a warning for missing resources (plus a nice obnoxious black and purple checkerboard which catches your attention and basically says "hey, dummy, this texture doesn't exist").
    Also in UE4, AFAIK you get a blank white placeholder for missing textures.


  • Banned

    @Masaaki_Hosoi said:

    I'm pretty sure Source at the very least logs a warning for missing resources

    I don't consider logging the error handling.



  • @dkf said:

    As every small child needs to learn for themselves about playgrounds: what time you gain on the swings, you lose on the roundabouts.

    And now I have that Marillion song in my head, though that was losing on both... Not sure if preferable to having C vs C++ in my head.


  • Discourse touched me in a no-no place

    @Gaska said:

    I don't consider logging the error handling.

    You should consider it to be part of the error handling, along with the fallback (or retry) strategy. It beats splurging everything in a dialog box that just looks ugly and which users can't do anything about (especially if they can't actually interact with the dialog). Or just exiting with no message or warning or logging or anything…


  • Banned

    @dkf said:

    You should consider it to be part of the error handling, along with the fallback (or retry) strategy.

    No. Logging isn't error handling. It's debug info. Might as well be no-op for all practical purposes. Error handling is when you actually do something in case of error. Displaying dialog box is error handling. Rolling back transaction is error handling. Logging error and continuing is NOT error handling. Not filling the data structure because file open failed, but then continuing to use this structure as if nothing happened, is not error handling either - it's ignoring it.



  • @Gaska said:

    @dkf said:
    You should consider it to be part of the error handling, along with the fallback (or retry) strategy.

    No. Logging isn't error handling. It's debug info. Might as well be no-op for all practical purposes. Error handling is when you actually do something in case of error. Displaying dialog box is error handling. Rolling back transaction is error handling. Logging error and continuing is NOT error handling. Not filling the data structure because file open failed, but then continuing to use this structure as if nothing happened, is not error handling either - it's ignoring it.

    I think your definition of error handling seems arbitrary.

    I would consider logging at least a descriptive warning, coupled with a fallback strategy (in the previous case 0x0 texture, some sort of checkerboard pattern, or whatever), should be considered error handling - it's handling the error and not letting the program shit itself, while still providing the developer with useful information (they can check the log files to know which textures were missing, for instance).



  • @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.

    And that's an understatement. Spirit takes C++'s syntax, tortures it, mutilates, rapes violently, murders, then rapes its violated corpse again.

    A simple EBNF grammar snippet: ``` group ::= '(' expression ')' factor ::= integer | group term ::= factor (('*' factor) | ('/' factor))* expression ::= term (('+' term) | ('-' term))* ``` is approximated using facilities of Spirit's Qi sublibrary as seen in this code snippet: ``` group = '(' >> expression >> ')'; factor = integer | group; term = factor >> *(('*' >> factor) | ('/' >> factor)); expression = term >> *(('+' >> term) | ('-' >> term)); ```

  • Banned

    @Masaaki_Hosoi said:

    I think your definition of error handling seems arbitrary.

    Not really. When you take a shower and you notice there's no soap, washing with fresh water alone isn't what I consider error handling. Getting a new soap, or using shampoo instead, is.

    @Masaaki_Hosoi said:

    I would consider logging at least a descriptive warning, coupled with a fallback strategy (in the previous case 0x0 texture, some sort of checkerboard pattern, or whatever), should be considered error handling

    0x0 texture and checkerboard pattern are absolutely different approaches - the first is continuing even in face of error because you can, without adding any steps compared to regular execution path, while second is actively taking measures to mitigate the damage. In short, ignoring vs. handling.


  • Discourse touched me in a no-no place

    @Gaska said:

    Logging error and continuing is NOT error handling.

    That depends on what else is happening. Sometimes it is right to not log the error because you've got a superior method of handling it that doesn't require later tracing at all.

    @Gaska said:

    Not filling the data structure because file open failed, but then continuing to use this structure as if nothing happened, is not error handling either - it's ignoring it.

    You seem to be assuming that I'm talking about a glorified ON ERROR RESUME NEXT. I'm not at all. Remember, I'm talking about handling the error in quite a different context to where it was issued; the exception will have also bubbled outwards and shouldn't be being caught in a context where there's a structure with undefined contents because that exception occurred. Exceptions — which may be implemented in many ways; there's genuine complexity here — are a lot less error prone than return codes for users of a programming language, provided they function well and are sensibly generated from the standard library. [spoiler]Go was a huge backwards step.[/spoiler]

    Of course, you might be using exceptional conditions wrongly and getting in a bad state “deliberately” but that's not what the rest of us are doing. (It ought to be a compile-time error for a variable to be read from where the compiler can't prove that it was written to first. I can understand why that can be hard, but I'm digressing…)


  • Banned

    @dkf said:

    That depends on what else is happening.

    If something else happens, then this something else is error handling. Logging is about as relevant to the application's workings as code comments.

    @dkf said:

    You seem to be assuming that I'm talking about a glorified ON ERROR RESUME NEXT. I'm not at all.

    You should, because texture loading is perfect use case for this.

    @dkf said:

    Remember, I'm talking about handling the error in quite a different context to where it was issued

    Looks like each of us is having a completely different conversation here. Whatever.

    @dkf said:

    Exceptions (...) are a lot less error prone than return codes for users of a programming language, provided they function well and are sensibly generated from the standard library.

    Also, provided that return codes function badly and standard library is using them in insane way. Because most of the problems with returning errors comes from lack of enough language support to make them good. If they're done right (like in Rust), it all comes down to a little bit more noisy invocation with error returns vs. more freedom with where to put error handling. And more freedom isn't always good (the only argument against goto is that it gives too much freedom - yet it made goto nearly extinct).


  • Banned

    @dkf said:

    It ought to be a compile-time error for a variable to be read from where the compiler can't prove that it was written to first.

    It is in Rust 😛


  • Discourse touched me in a no-no place

    @Gaska said:

    It is in Rust 😛

    And Java and C# and Haskell and… It's not exactly an unusual condition.


  • Banned

    @dkf said:

    And Java

    I'm pretty sure I can use object as soon as I declare it - though it will obviously be null...


  • Discourse touched me in a no-no place

    @Gaska said:

    If something else happens, then this something else is error handling. Logging is about as relevant to the application's workings as code comments.

    You have much still to learn, Grasshopper.

    @Gaska said:

    You should, because texture loading is perfect use case for this.

    So? That's really a very small minority of use cases. Most error handling does not need to happen in close proximity to the point where the error was detected, and is often (but not universally) best delegated to a much higher level of the code.

    @Gaska said:

    the only argument against goto is that it gives too much freedom

    The whole goto thing was originally an argument about some of the more horrible things that you can do in assembler and old-style FORTRAN and BASIC. Since that time, we've recognised that goto (and the closely related “basic block”) is a control structure primitive, and that most of the patterns that goto was used for are better written in a higher-level fashion. Exceptions are one of the mechanisms for doing that (as are while loops, multiple returns in a function, scope-bound resources, etc). Do we need to expose the primitive at all? Many people certainly believe not. I'm not quite as certain, though I sure don't miss it for 99.99% or more of my programming…


  • Discourse touched me in a no-no place

    @Gaska said:

    I'm pretty sure I can use object as soon as I declare it - though it will obviously be null...

    In Java, fields of reference type default to null (unless you specify an initialiser). Local variables have no default, and must be assigned before use.


  • Banned

    @dkf said:

    You have much still to learn, Grasshopper.

    Business requirements, Heisenbugs and detecting current application version aside, it's what I said.

    @dkf said:

    So? That's really a very small minority of use cases.

    But applicable in the particular one we're discussing here.

    @dkf said:

    Most error handling does not need to happen in close proximity to the point where the error was detected

    This is true of any programmin task.

    @dkf said:

    and is often (but not universally) best delegated to a much higher level of the code.

    Depends on how you look at it. If F1 calls F2 which calls F3 which calls F4 which encounters errors which can be handled meaningfully only in F1, you handle F4's error in F1. Me, on the other handle, see this error as originating from F2, because that's what F1 called - and is handling it in the same place it happened (from F1's POV).

    @dkf said:

    The whole goto thing was originally an argument about some of the more horrible things that you can do in assembler and old-style FORTRAN and BASIC.

    In other words, too much freedom.

    @dkf said:

    In Java, fields of reference type default to null (unless you specify an initialiser). Local variables have no default, and must be assigned before use.

    I stand corrected.


  • Discourse touched me in a no-no place

    @Gaska said:

    In other words, too much freedom.

    While I'm not disagreeing, I wouldn't exactly word it like that. Instead, I think it's too hard to analyse, either for computers or for humans. 😄 The computed goto is one of the most outright evil constructs ever invented in a programming language. The history of structured programming could pretty much be described in terms of attempts to tame it.


  • Banned

    Computed goto is the executable code's equivalent of pointer arithmetic. Both are useful sometimes, but only at a very low level. Back in 70s, it made perfect sense to have access to it.


  • Discourse touched me in a no-no place

    @Gaska said:

    Computed goto is the executable code's equivalent of pointer arithmetic. Both are useful sometimes, but only at a very low level. Back in 70s, it made perfect sense to have access to it.

    Trust me on this, computed goto is pretty much dead now even at pretty low levels. Specifically, at the level of LLVM IR (which is a sort of high-level assembler if you've not encountered it before) you've not got true computed goto. There's switch and indirectbr, but they're both quite constrained as you have to list out all the places you can jump to, so taming the nastiness (i.e., producing an analysable flow graph). A true computed goto now tends to only be supported if you go down to the true machine code level, and virtually everyone avoids that if they can.


  • Java Dev

    @Masaaki_Hosoi said:

    I would consider logging at least a descriptive warning, coupled with a fallback strategy (in the previous case 0x0 texture, some sort of checkerboard pattern, or whatever), should be considered error handling - it's handling the error and not letting the program shit itself, while still providing the developer with useful information (they can check the log files to know which textures were missing, for instance).

    Logging a warning only makes sense if you train your users to actually check the warnings - so log detectable problems as warnings as much as possible, and make sure you don't log things that don't need to be resolved, and that all warnings are resolvable. Then at an opportune time you can tell the tool user "<action> performed with <count> warnings" and the tool user knows these are real issues that are worth looking into.


  • Banned

    @dkf said:

    A true computed goto now tends to only be supported if you go down to the true machine code level, and virtually everyone avoids that if they can.

    My point exactly. Though I thought LLVM still allows jump to address at register. Thanks for this piece of useless trivia!


  • Discourse touched me in a no-no place

    @Gaska said:

    Though I thought LLVM still allows jump to address at register.

    Yes, that's what indirectbr does. However, you need to statically tell it all the possible places that the indirectbr could go to; the analysis engine treats all as equally possible (unless it can prove otherwise).


  • Banned

    It's quite different from regular jump. You can't for example conjure the jump destination out of thin air at runtime.



  • @Gaska said:

    Not really. When you take a shower and you notice there's no soap, washing with fresh water alone isn't what I consider error handling. Getting a new soap, or using shampoo instead, is.

    What about getting out and writing down "Get soap" on a todo list? ;)


  • FoxDev

    There's an botapp for that


  • Banned

    @Masaaki_Hosoi said:

    What about getting out and writing down "Get soap" on a todo list?

    If you're actually going to get soap, then yes, this is error handling. If, however, you made the note as a form of prayer to God or a letter of complaint, then no, you're not handling any errors.



  • @cartman82 said:

    Obviously, it's a contrived example, designed to expose the possibility.

    Deliberately creating a bad design and then using that to demonstrate a "problem" is a complete fallacy.



  • @Gaska said:

    @Masaaki_Hosoi said:
    What about getting out and writing down "Get soap" on a todo list?

    If you're actually going to get soap, then yes, this is error handling. If, however, you made the note as a form of prayer to God or a letter of complaint, then no, you're not handling any errors.

    "Dear god, I am out of soap. Please send some.
    PS
    Where the fuck do my left socks keep going? Please send some replacements for those too."



  • @Gaska said:

    If you're actually going to get soap, then yes, this is error handling. If, however, you made the note as a form of prayer to God or a letter of complaint, then no, you're not handling any errors.

    I have to disagree. Going with the analogy, if you pretend to grab the soap, pretend to use it, and then pretend to wash the suds off despite not having soap, then yes, you've ignored the error and not really handled it. If you say "huh, there's no soap. I guess I'll just do without until I can fix it, use extra deodorant, and add it to my shopping list", that is one error handling strategy. Maybe there are better alternatives, it all depends on your goals, priorities and costs. That comes down to design decisions. The point is, so long as the error was considered and a decisions was made, you've handled the error.

    The goal of error handling is not to respond to every possible error in a way that achieves your original goal. Not all errors are recoverable, so that definition is doomed to failure. The goal of error handling is ensuring the system transitions to a predictable state in case of errors.


  • Banned

    @Kian said:

    huh, there's no soap. I guess I'll just do without until I can fix it

    That's not error handling.

    @Kian said:

    use extra deodorant, and add it to my shopping list

    That is, however.

    @Kian said:

    The point is, so long as the error was considered and a decisions was made, you've handled the error.

    So we're now discussing semantics.


  • Discourse touched me in a no-no place

    @Gaska said:

    That's not error handling.

    You're a nitpicky nitwit who likes to quote half sentences at people, which is a terrible habit that is likely to end up getting people to punch you in the nose. No wonder you like Rust.

    The punching in the nose would be an excellent error handling strategy: apply corrective action and retry.


Log in to reply