6 ms·
There is nothing about exceptions that's inappropriate for system programming. If anything it enables the enforcement of strong invariants and leads to better
by mgaunard 4y ago
There is nothing about exceptions that's inappropriate for system programming.
If anything it enables the enforcement of strong invariants and leads to better and safer code.
- masklinn 4y ago> There is nothing about exceptions that's inappropriate for system programming. There's lots about exceptions which is inappropriate for system programming, starting from FFI unsafety and the lack of signaling to callers (which makes resilient use more difficult). > If anything it enables the enforcement of strong invariants It doesn't do that. > and leads to better and safer code. It only does that in comparison to truly deficient (e.g. c-style) error reporting, and that's being generous.
- mgaunard 4y agoWell it seems you don't understand exceptions. They eliminate erroneous states entirely, since the objects just don't get created if an error occurs. The alternative that the parent said was making all of your state be a union with some kind of error, and making sure all accesses handle the fact the variable might be in a erroneous state. That is a huge explosion of possible states in your program, and essentially making every invariant weak everywhere. Then FFI, I suppose you mean interfacing with C. Problems that arise when interfacing with other programming languages are orthogonal to a language's ability to be used for system programming. Obviously you wouldn't let an exception propagate through some C code, that's forbidden.
- zozbot234 4y agoYou don't need exceptions for constructors. You can just use static factory functions with an error return as Rust does, and dispense with constructors altogether.
- mgaunard 4y agoEnjoy your combinatorial explosion of states. It's not equivalent at all.
- Yoric 4y agoCould you elaborate? Rust manages pretty well without constructors and I'm nearly certain that it doesn't have any kind of combinatorial explosion of states. Same for ML-family languages.
- jcelerier 4y agoCan you enforce at compile-time that only such a static factory can ever be used for creating the object in Rust? This is the whole point of constructors, they cannot not be used. Otherwise you're just one commit away of creating an object that will not respect the invariants - will you even remember to call this specific factory function in 6 months?
- jstimpfle 4y agoTo me, constructors are mostly a convenience thing for the simple cases where I declare a quick container on the stack or similar, and don't want to waste keypresses to have it constructed. And to me it's a question of, what does the language want to be -- maybe this kind of code is better served by languages like C#. There are various practices that handle the problem of having to call a specific function to get an object in a specific state, without requiring language support. You can make the function that should be used stand out in an obvious way. You can hide the definition of a structure, which very If it is the whole point of constructors to guarantee that the object is in the right state, it could be the best argument why Rust does not have constructors. Programming is chock-full of "this must be called only by that or in this or that context..." and practically speaking, only a part of them can be handled by language objects and construct/deconstruct semantics. Plus, C++ gives enough escape hatches to get not-constructed objects or to un-construct objects without them going out of scope. These guarantees that C++ provides (but not really), they require a ton of baggage like ridiculous initializer lists or 17 different kinds of constructors (to the point where it's sometimes almost impossible to tell which will be called), or requiring an out-of-band mechanism (exceptions) to signal construction failure.... that's not worth it in my book.
- masklinn 4y ago> Well it seems you don't understand exceptions. They eliminate erroneous states entirely, since the objects just don't get created if an error occurs. Error sum types do the exact same thing. > The alternative that the parent said was making all of your state be a union with some kind of error, and making sure all accesses handle the fact the variable might be in a erroneous state. Which is a non-issue as it is lifted to the type system. The type system will not let you forget about that. > That is a huge explosion of possible states in your program, and essentially making every invariant weak everywhere. You get an error state added to a given value, which you also get via exceptions, except implicitly and without notification of the additional state. Type-safe error values also provide simpler error handling and recovery in many case, because they don't require split-path handling. > Then FFI, I suppose you mean interfacing with C. Problems that arise when interfacing with other programming languages are orthogonal to a language's ability to be used for system programming. It very much isn't, part of the system programming workload is to provide reusable components. > Obviously you wouldn't let an exception propagate through some C code, that's forbidden. And rarely if every checkable statically, hence unsafe.
- mgaunard 4y agoThe problem is not "forgetting about it", it's that it increases the possible values of your working set. If you have 3 variables, each of which can be in 10 states, that's 10^3 states your program can be in. If instead you have 11 states because all your variables are actually unions with an error, that's 11^3 states (assuming all error states are equivalent to a single state). Now in practice it's even worse since what you care about isn't the possible states of your values, but rather how many different paths you have in your control flow to handle them. Then you're comparing 1 (none of my values are in an erroneous state) with 2^3=8 (any of my values can be in an erroneous state or not). What exceptions do is enforcing that your working set does not have to encode any erroneous states, preventing the combinatorial explosion of states, which of course is a net win, there isn't really any valid argument that can be made against it. Where people are debating is that sometimes you do want errors to be part of your working set, in which case you shouldn't use exceptions. But choice is difficult for some, especially those seeking absolute doctrines. > It very much isn't, part of the system programming workload is to provide reusable components. That's already somewhat dubious, since a lot of system programming tasks are really purpose-built for a usecase or for specific hardware, and regardless, there is nothing about that which has anything to do with interfacing with C. I do a lot of system programming and I write it all in C++, which has a lot of advantages over C beyond exceptions.
- mike_hock 4y ago> They eliminate erroneous states entirely, since the objects just don't get created if an error occurs. EPIPE Erroneous states can arise outside of your control at any time.
- jcelerier 4y agoSignaling to caller has already been shown to be a complete mistake with java checked exceptions, I don't understand why people persist with this. It makes for ridiculous code.
- jstimpfle 4y agoAre you saying that "not signaling" has not been shown to be a total maintenance nightmare as well? Because the only way it is not ever a maintenance nightmare is when you really don't care that function execution could be aborted at any point without any visual clue. And the only way I can see you wouldn't care is when you go 100% in to everything-context managed / RAII or similar. And that again, sorry but I can't be arsed to prematurely rip everything into little pieces like that. It makes for terrible code IMO.
- mike_hock 4y agoBut you can be "arsed" to write an error handler after almost every single function call?
- jstimpfle 4y agoI explained here that I consider having to check many return values to be a smell: https://news.ycombinator.com/item?id=34217251 https://news.ycombinator.com/item?id=34217251 And from my own experience, no I don't have to check a lot of return values.
- masklinn 4y ago> Signaling to caller has already been shown to be a complete mistake with java checked exceptions It's not signalling which has been shown to be a complete mistake, but checked exceptions, more specifically as implemented by Java. Signalling works just fine and is actually rather enjoyable when done well.
- kllrnohj 4y agoCan you point to an example? What has "signaled exceptions"?
- blub 4y agoExceptions have their uses, including in system programming. There’s in fact nothing about system programming which makes a particular error handling method better or worse. These are the kind of minor points some programmers like to fixate on and then sell as the one true way of doing X, while providing no proof and asking everyone to trust them, because it worked great in a project once for the comment author. Not letting exceptions escape at API boundaries has been a technique for a few decades. It’s not rocket science. Writing exception-safe code is likewise an ancient technique by now. Meyers’ books which explained such things were published in the 90s…
- the_why_of_y 4y agoThis may be true for checked exceptions, but certainly not for unchecked exceptions.
- mgaunard 4y agoAll exceptions must be caught to satisfy lifetime invariants in multi-threaded contexts.
- the_why_of_y 4y agoOf course. With unchecked exceptions, it's impossible to statically check that this is the case, so for system programming they are not an appropriate error handling mechanism.
- mgaunard 4y agoNone of system programming can be statically checked, since it's mostly about I/O with weak memory models.
- Yoric 4y agoCaveat: we may be using different definitions of "system programming". The one I'm using is code that is fairly close to the system, i.e. will call into libc or into the kernel/libSystem/etc. as well as calling more-or-less directly into a bunch of system-specific .so/.dylib/.dll. In my experience as a system developer, you need to invest some time into understanding the invariants expected/promised by the libraries you're calling, but many of them map nicely to static types. Of course, if you're implementing e.g. a IPC or RPC layer, you need to deserialize (and validate along the way) your inputs, but there are very few systems that do not need to do that regardless.
- mgaunard 4y agoCode that uses libc is just normal code.
- codeflo 4y ago> enables the enforcement of strong invariants My experience has been the opposite. Ensuring exception safety in a type that has nontrivial move/copy operators (that is, a type that for whatever reason can’t follow the “rule of zero”) is often a research-level problem. Not having to worry about that in Rust is such a breath of fresh air.
- mgaunard 4y agoEnforcing basic exception safety is trivial, you just have to follow very simple rules. Enforcing strong exception safety might require some thought, but it's definitely not "a research-level problem". In any case either of these is miles easier than satisfying the Rust borrow checker, unless you use the cop-out of (A)rc. Regardless, how the invariants of your objects are maintained in case of operation failure is something you should be thinking of regardless of the language.
- Yoric 4y ago> Enforcing basic exception safety is trivial, you just have to follow very simple rules. That's not my experience as a C++ developer in a complex, cross-platform, application, which needs to: 1. interact with C; 2. operate with an event loop; 3. operate/interact with a GC; 4. interact with non-trivial system libraries (e.g. Direct3D, Vulkan, ...) > In any case either of these is miles easier than satisfying the Rust borrow checker, unless you use the cop-out of (A)rc. The borrow checker is indeed complicated. I'm not sure how you define "satisfying", though. As for (A)rc, it can definitely be interpreted as a "cop-out" or as delaying optimization until you actually have good reasons to believe that you need it.
- mgaunard 4y agoI'm sorry to hear that you haven't been successful in using C++ features to their full potential in environments tightled coupled with C libraries. Integration with C or C-like code usually requires some effort if you want to be able to use exceptions that could propagate through C. I do not offer consulting but can refer you to people who do.
- kajaktum 4y ago>If anything it enables the enforcement of strong invariants and leads to better and safer code. How?? If a container have `get(K)->V` and `remove(K)->V` then how does it preserve this invariant? This is an impossible contract to satisfy once you try to push once and pop twice. The container is promising you something that it can't satisfy, I would rather have a container that's honest with `get(key)->Maybe(value)`.
- mgaunard 4y agoYou have it backwards, the strong invariant is that you have a container and not a maybe container.