Jonathan M Davis ![Jonathan M Davis's Gravatar profile Gravatar of Jonathan M Davis](//www.gravatar.com/avatar/c2372c89dbe85590c9c1dd06c6f998e0?d=identicon&s=80)
Posted in reply to ryuukk_
| On Sunday, February 4, 2024 3:13:39 AM MST ryuukk_ via Digitalmars-d wrote:
> EH is evil, some platforms has banned it completly (apple), and microsoft is all in rust, they are rewritting their C# application in Rust too, not just C++, it is an inferior design
>
>
> https://www.theregister.com/2024/01/31/microsoft_seeks_rust_developers/
A number of us do not agree with you, and I for one never will. For many situations, exceptions are by far the best error handling mechanism that there is. D's implementation of it could certainly use some improvements (e.g. while being able to have a hierarchy for exceptions is valuable, the fact that they're classes and thus have to be heap allocated is a problem for code that needs to avoid the GC), so it would be great if D's implementation for exceptions could be improved, but the basic concept is solid and makes a lot of code cleaner as a result (e.g. I'd never want to write a parser that didn't use exceptions; being able to separate the error handling code from the actual parsing makes the code _far_ cleaner and far less error-prone than any other error handling mechanism that I've ever encountered).
Obviously, there are situations where exceptions are not appropriate (e.g. it was definitely a mistake to have Phobos decoding Unicode all of the place and potentially throw a UTFException from practically anywhere as a result), and any properly written library is going to have to be intelligent about when any particular error handling mechanism is used, but exceptions are far, far too useful to not use - especially when the main alternative is checking return values all over the place. So, while there are places that Phobos currently uses exceptions where it shouldn't, there are others where using them is very much the right thing to do IMHO, and I would use them again in the next version of Phobos (and argue strongly against anyone trying to not use them there), because if I didn't, the result would be worse code that was harder to use.
In general, exceptions are best when
1. It's cleaner to assume that an operation will succeed, and it's reasonable to assume that it will succeed. Parsing is a great example of this. You're not going to do something like validate that an XML document is going to parse correctly and then parse it, since that would basically mean parsing it twice. You're just going to parse it and then report when that fails so that the caller can handle it. And exceptions make that very clean, because then you only have to have the one place at the top that catches the exception, and the rest of the code can just do its thing. The way that exceptions bubble up, allowing code along the way to completely ignore the error handling and then have somewhere higher up the stack that is actually in a position to handle the error condition catch the exception and handle it is an amazing feature.
2. It's also best to use exceptions when you can't guarantee that an operation will succeed but where it's reasonable to assume that it will - particularly when it's actually impossible to check that an operation will succeed beforehand. A great example of this would be reading a file. It's good practice to check that the file exists first, but even then, you can't guarantee that opening it will succeed, because the file could be removed between the time you check and the time you go to open it (or a variety of other errors could occur which cause opening or reading the file to fail). The same goes for most file operations really. Any of them could fail, and there needs to be a way to report that, and it clutters up the code considerably if you have to deal with checking return values all over the place - on top of the fact that it makes it impossible to simply return an object or buffer from a function and use it if you also have to check an error condition. You're stuck either returning the object via one of the parameters or using a compound type where you have to check if the operation succeeded and then extract the actual return value from it, which absolutely destroys your ability to chain function calls - on top of making it far more likely that code will fail to check the return value like it should, resulting in it assuming that the call succeeded when it didn't.
3. For constructors, exceptions are really your only option unless you want to get into doing two-part initialization, which is known to be error-prone, and the advice I've almost always seen is to avoid it like the plague. So, if a constructor needs to validate its arguments - or report any other error condition that might occur while it's being constructed - an exception is going to be the way to do it unless you want to do something like add a member function to the type just so that you can check whether it was constructed correctly, and at that point, you might as well just be doing two-part initialization.
4. And exceptions are an excellent choice in any situation where you need to be sure that an error condition is not ignored - or at least put the code in a position where either the exception is caught and handled in whatever manner is appropriate, or the program is killed, because the error condition wasn't handled. Obviously, not all error conditions fall into that category, but there are plenty of situations where error conditions must be handled for the application to operate properly, and that's much more likely to happen with an exception than with any other error reporting mechanism that D has. Some other languages force that by forcing you to check return values, but that can be incredibly annoying, and it results in far more verbose code, because you have to put error-handling code everywhere instead of just letting the exception bubble up to the code that can actually handle it properly.
Now, obviously, there are situations where exceptions aren't appropriate. An obvious one is where an error is likely, because using an exception in that kind of situation is going to be inefficient in comparison to other options, and if the error path is that likely, then trying to separate out the error handling code like you do with try-catch statements really doesn't make sense anyway.
And of course there are situations where validating stuff ahead of time is perfectly reasonable and allows the code beyond that to just assume that everything works without needing to report error conditions at all, because the validation was already done. Unicode decoding is one such case where that often makes sense. If you validate the Unicode when you read in a file, then the rest of the code doesn't have to. Phobos, unfortunately, currently picks the worst of both worlds, because readText checks the Unicode, and then you have auto-decoding all over the place which validates the text again and throws an exception if it's invalid. So, we end up with a bunch of code that can't be nothrow or @nogc just because of a potential UTFException which can't possibly be thrown if the text was already validated.
And of course there are also situations where it's actually reasonable to ignore an error, in which case, throwing an exception wouldn't make sense - but no other reporting mechanism would either. Unicode can actually be handled in such a fashion in some cases, because it has the replacement character so that you can use it anywhere that you encounter an invalid code point, which gives text processing a way to process text without worrying about invalid Unicode. That's obviously not a good choice in all cases, but without auto-decoding, that choice is left up to the programmer as it should be.
And with D, there are situations where exceptions can't be used where they could be in a language like C++, because D's exceptions are allocated on the heap, which not only means allocating memory, but it typically means using the GC, which is fine for most code but not fine for all code. So, anyone in that kind of situation is going to need to avoid exceptions even if they would otherwise be the best choice.
And naturally, there are idiots who use exceptions for stupid things (e.g. throwing an exception when you're checking a condition instead of just returning bool, or having a type implement an interface and then throw an exception if you call a function that it doesn't actualy support), which I expect is part of the reason that some folks dislike exceptions. So, there's no question that exceptions can be a problem, but overall, in the hands of someone who knows what they're doing, they can be an excellent tool and result in much cleaner, less error-prone APIs.
Obviously, you're free to hate exceptions for whatever reasons you may have, and we're obviously not going to use them for everything in Phobos, because that would be dumb, but given that there are cases where code is clearly cleaner and easier to use correctly when exceptions are used, you're not going to find general agreement with the idea that exceptions are inherently bad. I expect that for most people, the disagreement is going to be on which cases exactly they make sense for, and which they don't, not whether they're a bad idea in general.
And personally, I wouldn't use D if it didn't have exceptions. It would be far too miserable to not have them. So, while D's exceptions could certainly use some improvement, I never would have started using D if it hadn't supported exceptions, and if Walter seriously tried to get rid of them, I'd go find another language to use instead. The exact form exceptions in D take may change at some point in the future, but their basic functionality is IMHO critical to writing good APIs, and part of the reason that languages like C are miserable to use is that they don't have a comparable feature.
- Jonathan M Davis
|