[HN Gopher] An epic treatise on error models for systems program...
       ___________________________________________________________________
        
       An epic treatise on error models for systems programming languages
        
       Author : transpute
       Score  : 185 points
       Date   : 2025-03-08 04:46 UTC (18 hours ago)
        
 (HTM) web link (typesanitizer.com)
 (TXT) w3m dump (typesanitizer.com)
        
       | Galanwe wrote:
       | > Almost all catastrophic failures (92%) are the result of
       | incorrect handling of non-fatal errors explicitly signaled in
       | software
       | 
       | So in a laps of 15 years (2010-1025), they hand picked 20 bugs
       | from 5 open source filesystem projects (198 total), and
       | extrapolated this result.
       | 
       | That is not science.
        
         | dbtc wrote:
         | It is writing, though. You've picked one sentence out of rather
         | more and made a negative statement about it. What is that?
        
           | perching_aix wrote:
           | Critique. The idea is that the evaluation in the article may
           | not have been representative, but this quote is self-
           | contained enough to be representative of its own stance.
           | 
           | Instead of going for rhetorical KOs, how about remaining
           | constructive? You're well aware that this is what they meant,
           | you could have just written it plain out if you disagree.
        
         | typesanitizer wrote:
         | Hi, blog post author here (unrelated to paper authors).
         | 
         | > So in a laps of 15 years (2010-1025),
         | 
         | The paper was published in 2014, so the period is 2010-2014,
         | not 2010-2025.
         | 
         | > they hand picked 20 bugs from 5 open source filesystem
         | projects (198 total)
         | 
         | The bugs were randomly chosen; "hand picked" would imply that
         | the authors investigated the contents of the bug reports deeply
         | before deciding whether to include them (which would certainly
         | fall under "bad science"). The paper states the following:
         | 
         | > We studied 198 randomly sampled, real world fail- ures
         | reported on five popular distributed data-analytic and storage
         | systems, including HDFS, a distributed file system [27]; Hadoop
         | MapReduce, a distributed data- analytic framework [28]; HBase
         | and Cassandra, two NoSQL distributed databases [2, 3]; and
         | Redis, an in- memory key-value store supporting master/slave
         | replica- tion [54]
         | 
         | So only 1 out of 5 projects is a file system.
         | 
         | > and extrapolated this result. That is not science.
         | 
         | The authors also provide a measure of statistical confidence in
         | the 'Limitations section'.
         | 
         | > (3) Size of our sample set. Modern statistics suggests that a
         | random sample set of size 30 or more is large enough to
         | represent the entire population [57]. More rigorously, under
         | standard assumptions, the Central Limit Theorem predicts a 6.9%
         | margin of error at the 95% confidence level for our 198 random
         | samples. Obviously, one can study more samples to further
         | reduce the margin of error
         | 
         | Do you believe that this is insufficient or that the reasoning
         | in this section is wrong?
        
           | Galanwe wrote:
           | > The paper was published in 2014, so the period is
           | 2010-2014, not 2010-2025.
           | 
           | Oh indeed, my bad.
           | 
           | > The bugs were randomly chosen; "hand picked" would imply
           | that the authors investigated the contents of the bug reports
           | deeply before deciding whether to include them
           | 
           | No, no, they were not randomly chosen, they even have a whole
           | paragraph explaining how they randomly picked _from a pool of
           | manually selected bugs_. Their criterion of selection varied
           | from "being serious", to "having a lot of comments" and "they
           | can understand the patch", or "the patch is not from the
           | reporter".
           | 
           | > The authors also provide a measure of statistical
           | confidence in the 'Limitations section'.
           | 
           | This is a measure of confidence of their random sampling
           | being representative of the hand picked bugs...
           | 
           | > under standard assumptions, the Central Limit Theorem
           | predicts a 6.9% margin of error at the 95% confidence level
           | 
           | I would love to see them prove the normality assumption of
           | bug root cause distribution.
           | 
           | Also, the whole categorization they do seem purely
           | qualitative.
           | 
           | This paper seems to lack rigor to me.
        
             | gtirloni wrote:
             | > The whole paper is a statistical joke.
             | 
             | I have been guilty of such kind of comments and I have come
             | to realize they do nothing to further my point. I would
             | suggest to read the guidelines because they are very well
             | written about this:
             | https://news.ycombinator.com/newsguidelines.html
        
               | Galanwe wrote:
               | Fair point, I edited my answer to be less dismissive of
               | the paper.
               | 
               | Thanks for the considerate feedback.
        
           | epr wrote:
           | > We studied 198 randomly sampled, real world fail- ures
           | reported on five popular distributed data-analytic and
           | storage systems, including HDFS, a distributed file system
           | [27]; Hadoop MapReduce, a distributed data- analytic
           | framework [28]; HBase and Cassandra, two NoSQL distributed
           | databases [2, 3]; and Redis, an in- memory key-value store
           | supporting master/slave replica- tion [54]
           | 
           | So, analyzing databases, we picked Java, Java, Java, Java,
           | and one in C. This does not seem very random. I suppose this
           | may provide insight into failure modes in Java codebases in
           | particular, but I'm not sure I'd be in a hurry to generalize.
        
       | tuukkah wrote:
       | From the end:
       | 
       | > _Pithy statements such as "only use exceptions for exceptional
       | cases" or "Let it crash", while catchy, do not do justice to the
       | complexities that programmers need to deal with when deciding how
       | to define, propagate, handle and reason about errors._
       | 
       | I think the author is on to something here w.r.t. how current
       | programming languages and conventions brush over what good code
       | might need.
        
       | nahuel0x wrote:
       | Missing piece: Common Lisp continuable condition system.
        
         | nesarkvechnep wrote:
         | More like restartable. You may be conflating it with
         | continuations.
        
         | typesanitizer wrote:
         | Copying my comment from the Lobste.rs thread (https://lobste.rs
         | /s/az2qlz/epic_treatise_on_error_models_for...)
         | 
         | > Hi, author here, the title also does say "for systems
         | programming languages" :)
         | 
         | > For continuations to work in a systems programming language,
         | you can probably only allow one-shot delimited continuations.
         | It's unclear to me as to how one-shot continuations can be
         | integrated into a systems language where you want to ensure
         | careful control over lifetimes. Perhaps you (or someone else
         | here) knows of some research integrating ownership/borrowing
         | with continuations/algebraic effects that I'm unfamiliar with?
         | 
         | > The closest exception to this that I know of is Haskell,
         | which has support for both linear types and a primitive for
         | continuations. However, I haven't seen anyone integrate the
         | two, and I've definitely seen some soundness-related issues in
         | various effect systems libraries in Haskell (which doesn't
         | inspire confidence), but it's also possible I missed some
         | developments there as I haven't written much Haskell in a
         | while.
        
           | ForTheKidz wrote:
           | Yes, I can see how if you view lifetime management as core to
           | systems processing lisps might be unappealing to you.
        
           | fanf2 wrote:
           | You don't need continuations to implement resumable errors.
           | The trick is simply to not unwind the stack when an error
           | happens.
           | 
           | http://axisofeval.blogspot.com/2011/04/whats-condition-
           | syste...
        
           | zozbot234 wrote:
           | You can use async to implement continuations, and async is
           | very much included in quite a few 'systems languages'.
        
       | namaria wrote:
       | You know, there is a programming language based on a PhD thesis
       | on fault tolerance. This programming language is built around a
       | VM that implements lots of great design on error handling. And
       | there is a functional programming language that compiles to the
       | bytecode that this VM runs. It is a delight to use, gives you all
       | the fault tolerance goodness for free, but the name is often
       | cursed in HN threads for some reason.
       | 
       | edit: I might be giving some random episode too much weight.
       | Seems like Elixir and Erlang are actually quite well liked around
       | here
        
         | ejiblabahaba wrote:
         | Which language is this? I'm sure some people can clue this
         | together from the hints, but I'm not one of them.
        
           | jasinjames wrote:
           | Probably Java/Clojure
        
             | namaria wrote:
             | I meant Erlang/Elixir
             | 
             | Java is also based on a doctorate research on fault
             | tolerance?
        
               | nesarkvechnep wrote:
               | No, God, no. Java isn't based on academic research.
        
             | flir wrote:
             | Erlang/Elixir is also a possibility.
             | 
             | But I don't think either Java or Erlang were based on a PhD
             | project?
             | 
             | (Edit: I just looked it up, and Joe Armstrong's PhD was
             | 2003)
        
           | kbn wrote:
           | I think erlang
        
           | Philpax wrote:
           | Erlang / BEAM would seem to fit, but I haven't seen much
           | disdain for it from HN posters; quite the opposite, actually.
        
             | pavlov wrote:
             | Yeah, it's a surprising take because Erlang originally used
             | to be a central object of reverence -- some would say
             | fetish -- on HN.
             | 
             | There was "Erlang Day" when the entire HN front page was
             | about Erlang because pg had made some offhand request for
             | more technical content:
             | 
             | https://news.ycombinator.com/front?day=2009-03-11
             | 
             | This was nearly 16 years ago apparently... (In my mind it's
             | in the "sometime before Covid happened" bucket. Sigh.)
             | 
             | Maybe it's time for another Erlang Day this March 11?
        
               | flir wrote:
               | Coincidence: I was just noodling around youtube and saw
               | that "Still Alive" from Portal was added 17 years ago.
               | I'm pretty sure I've discovered a date bug in youtube.
        
               | eru wrote:
               | I think Erlang day was mostly a joke to make the website
               | more 'boring' to wannabe l33t h4x0rs.
        
               | namaria wrote:
               | > some would say fetish
               | 
               | And there it is
        
               | pavlov wrote:
               | But it's all fetish-positive -- people really do admire
               | the language and the runtime.
        
               | dmix wrote:
               | Wow 16 years ago. I remember that day.
        
               | atombender wrote:
               | I think OTP is pretty much universally admired, the
               | language syntax and tooling much less so.
               | 
               | Erlangers generally dismiss criticisms about syntax as
               | unimportant, but looking at the popularity of Elixir
               | (which compiles to the same VM) suggests they're wrong
               | about discounting syntax as important when it comes to
               | adoption. Another new language that targets the BEAM VM
               | is Gleam, which looks fantastic.
               | 
               | From reading Armstrong's stories about the origins of
               | Erlang, it seems to me the Prolog syntax was an accident
               | of history and a pragmatic engineering decision at the
               | time, more than it was the deliberate choice of a careful
               | language designer. That suggests that other options
               | shouldn't be dismissed.
        
             | namaria wrote:
             | I seem to remember a phase when talking about it was
             | denounced as vitriol, together with praising Rust. Maybe it
             | was just a phase, maybe I took some random criticism too
             | seriously.
        
               | dcminter wrote:
               | Really!? I must have missed that; I genuinely thought you
               | must be talking about Java but couldn't make the
               | description fit!
        
         | jesperwe wrote:
         | Don't forget Gleam! It is young, but has a less steep learning
         | curve than Erlang/Elixir. And it is strongly and soundly typed,
         | with errors as values.
        
         | typesanitizer wrote:
         | Hi, author here. I'm a big fan of Armstrong's work, I've
         | watched several of his talks multiple times and always get
         | something new out of them even if I don't agree entirely. :)
         | 
         | I do mention Erlang near the start of the post, around the 360
         | word mark:
         | 
         | > Joe Armstrong's talk The Do's and Don'ts of Error Handling:
         | Armstrong covers the key requirements for handling and
         | recovering from errors in distributed systems, based on his PhD
         | thesis from 2003 (PDF) [sidenote 3].
         | 
         | > [sidenote 3] By this point in time, Armstrong was about 52
         | years old, and had 10+ years of experience working on Erlang at
         | Ericsson.
         | 
         | > Out of the above, Armstrong's thesis is probably the most
         | holistic, but it's grounding in Erlang means that it also does
         | not take into account one of the most widespread forms of
         | static analysis we have today - type systems
        
           | dmix wrote:
           | When I was reading your article my first thought was most
           | people would only read the intro but many don't even do that
           | before commenting.
           | 
           | Given how prevalent just reading the headline -> immediately
           | posting a preconceived take is, most information is probably
           | communicated through the top comments these days.
        
       | chrishill89 wrote:
       | What a great resource.
       | 
       | I've been in the errors-as-values (like Rust and Haskell) camp
       | for over a decade. But I think you need to go a bit beyond that
       | philosophy (i.e. not just as a slogan) in order to truly unlock
       | what treating errors as values can be like.
       | 
       | Errors as values are just values. Not any more complex than other
       | values. But except in cases like `None | Error` (no regular value
       | or an error) you at least double the work that you need to do.
       | The thing about the "happy path" is that it's just one channel of
       | information. With errors you layer on one more channel.
       | 
       | I think that has lead me to be careful about treating errors in
       | the same way I treat other kinds of values. Because simplifying
       | errors down to just one value, like a string, simplifies the
       | whole error channel. But how is error-as-string useful if you are
       | using the code as a library and not just erroring out and letting
       | the end-user/operator read it with their primate brain? Well that
       | lead me to add variants, sum types for all the different error
       | possibilities. Along with associated data like how this int was
       | larger than some exected bound.
       | 
       | Already here I run into a sort of sub-error problem: only
       | advertising what variants of an error that I can return from a
       | function. It seems that this isn't supported directly in Rust,
       | which is what I was using at that time. So if I just advertise
       | that I can return all the error variants (which is a soft lie)
       | then I might have to deal with them downstream.
       | 
       | More expressive error values seem to both (1) burden the
       | implementation (sub-errors) and (2) the clients who have to
       | consume them.
       | 
       | So do I use less expressive errors so that they become easier to
       | handle for client code?
       | 
       | Well it seems here that I'm still stuck treating errors as
       | special values. Because I still seem hesitant to treat errors in
       | their full generality as values. Think of a value:
       | 
       | 1. It's not just a single "atom"/scalar like an integer
       | 
       | 2. It can be in a list or a dictionary/map or on a stack or some
       | other data structure
       | 
       | 3. It can be transformed with a map or reduce operation since you
       | either want to transform the error or leave out some details
       | 
       | Maybe the library should provide a list of errors. But the client
       | only cares about the last one. So it takes the head of the list.
       | Maybe the library should provide a detailed error containing
       | associated data about the bounds, expected and actual. But the
       | client just cares about the error type and elides the rest.
       | 
       | Maybe this doesn't seem useful for errors. But take the `Hurdle`
       | type in the article. These are effectively a mandatory (product
       | type) list of warnings. Maybe the client code only cares about
       | five out of eight of them so he filters out the rest. Or he
       | doesn't care about them at all so he just ignores them.
       | 
       | And this seems to lead into iterators and lazy evaluation. In
       | high level languages at least we've come to a point where we
       | value pretty functional, declarative, and coarse transformation
       | of data. "Coarse" in the sense that we use a few datastructures
       | and operations to process the data without worrying about special
       | datastructures and micromanaging control flow. But crucially
       | these constructs don't have to allocate N intermediary
       | collections. They can be lazy. So maybe errors (and warnings
       | (hurdles)) should be as well. Maybe you can have a very rich
       | error/warning declaration while only generating them when the
       | client code wants it. And what happens when you make a nod (at
       | least) that all the error computations can be elided if only,
       | say, you just kill the application on the first sight of any
       | problems (like in a script)? People are incentivized to treat
       | errors as any other value without fearing that it is wasted
       | effort (computation).
       | 
       | Now on the other hand I do program in Java. And personally,
       | philosophically, I am fine with exceptions for the type of
       | application advocated in the article: report errors way up the
       | stack. But there I've been running into the same problem:
       | treating exceptions as not-quite-values. For any other custom
       | class I would create however many fields and methods I need. But
       | for custom exceptions I just give them a name. And maybe pass a
       | string to the constructor. Which I could do for a built-in or
       | third-party error. (So effectively I just give them a new name.)
       | 
       | But why? There must have been a _block_ in my mind. Because Java
       | exceptions are just classes. And they can have fields. And
       | ultimately we log errors that bubble all the way up. So why not
       | record all the associated data as fields on the exception and use
       | the error handler (with `instanceof`) to gather up all the data
       | into a structured log? That should be a ton better than just
       | creating format strings everywhere and sticking the associated
       | data into them. And of course you can do whatever else with the
       | exception objects in the error handler; once it's structured you
       | can use the structure directly, without any parsing or indirect
       | access.
        
         | demurgos wrote:
         | Great reply and it mirrors some of my reflections.
         | 
         | For Rust errors, I consider defining a single error enum for
         | the crate or module to be an anti-pattern, even if it's common.
         | The reason is because of what you described: the variants no
         | longer match what can actually fail for specific functions.
         | There's work on pattern types to support subsets of enums, but
         | until then I found that the best is to have dedicated errors
         | per pub function.
         | 
         | I also feel that there's an ecosystem issue in Rust where
         | despite "error as values" being a popular saying, they are
         | still treated as second class citizens. Often, libs don't
         | derive Eq, Clone or Serialize on them. This makes it very
         | tedious to reliably test error handling or write distributed
         | programs with strong error handling.
         | 
         | For Java, my experience with Exceptions is that they don't
         | compose very well at the type level (function signatures). I
         | guess that it's part of why they're used more for coarse
         | handling and tend to be stringy.
        
         | marcosdumay wrote:
         | Oh, here comes my usual rant about "algebraic" sum types that
         | break all of the algebra rules on sums...
         | 
         | On those languages, if you define the types X = A + B and Y = A
         | + B, and if you try to match X = Y, they don't match at all.
         | Besides, they don't compose (X = A + B, Y = X + C, Y = A + B +
         | C is false).
         | 
         | The result is that if you go and declare `read :: Handler ->
         | Either FileAccessError String`, you can't just declare your
         | function as `f :: String -> (FileAccessError | NetworkError |
         | MyCustomError)` expect it to work well with the rest of your
         | code. Because every time you need a different set here, you'll
         | have to declare a different type, and transform the data from
         | one type to the other.
        
           | nikajon_es wrote:
           | I don't understand how they don't compose, from your example:
           | (X = A + B, Y = X + C, Y = A + B + C is false)
           | 
           | I understand that types aren't math values, but isn't the
           | point of using a `+` to describe the communicative value of
           | the type so that `(A + B) + C = A + B + C`?
           | 
           | Also
        
             | marcosdumay wrote:
             | data A = A
             | 
             | data B = B
             | 
             | data X = XA A | XB B
             | 
             | data Y = YA A | YB B
             | 
             | f :: X -> () f = undefined
             | 
             | let r = f (undefined :: Y)
             | 
             | There's absolutely no way to write this so it compiles. In
             | fact, there isn't even a way to define the composed types
             | so that they only express a sum, you have to add extra
             | baggage.
        
           | looperhacks wrote:
           | The "sum" and "product" is more about the cardinality of the
           | type. If you have a type A with 3 possible values and B with
           | 5 possible values, then the sum type X = A+B has 8 possible
           | distinct values while the product type Y = A*B has 15
           | possible values
        
             | marcosdumay wrote:
             | Yeah, that's the bad definition that leads to problems like
             | the one the OP describes.
        
           | gf000 wrote:
           | You would want union types for that, they behave more like
           | what you have in mind. (Scala 3 has such a feature, as an
           | example)
           | 
           | Also, Rich Hickey's Maybe Not talk is sort of about this
           | "non-composability" of ADTs. E.g. a function accepting a
           | String can be relaxed to accept `String | None` without
           | recompile, but changing it to `Maybe<String>` would make a
           | refactor necessary.
           | 
           | At the same time Maybe<Maybe<String>> may encode a useful
           | state that will be unrepresentable with `(String | None) |
           | None = String | None`.
        
             | marcosdumay wrote:
             | Yes, unions are the proper type sums. Just as fixed
             | cardinality sets are the proper type products, not tuples.
             | I didn't know about this feature in Scala 3, all it seems
             | to be missing for a fully algebraic sum is type negation
             | (like `FileError and not FileSeekUnsupportedError`).
             | 
             | > At the same time Maybe<Maybe<String>> may encode a useful
             | state that will be unrepresentable with `(String | None) |
             | None = String | None`.
             | 
             | We already have the operation that takes a type and
             | converts it into a non-matching type. It doesn't have a
             | well accepted name, in Haskell it's called `newtype` but
             | every language with strict types has it.
             | 
             | There is no need to mix those operations in the fundamental
             | types in a language. The usual emuns and tuples are just
             | less expressive suggared synonyms.
        
       | demurgos wrote:
       | Very nice article. I agree with the theses and the resulting
       | design makes sense.
       | 
       | Regarding context handling, one issue that I struggle with is
       | where the context should be attached: by the caller or callee? In
       | particular, in the context of intermediate libraries, I would
       | like to limit needless work attaching the context if the caller
       | already handles it (some sort of opt-out).
       | 
       | Usually I just attach the context in the callee as it's the
       | simplest, but I'd like some systematic/reusable approach that
       | would control this. It would be nice if it fits in the type
       | system too. I use a bunch of languages like Rust, TypeScript,
       | Kotlin, Python and it feels like a larger design issue.
       | 
       | To give a more concrete example. In Node, `fs.readSync(path)`
       | includes the error path when it fails: the callee attaches the
       | context to the error. In Rust, `std::fs::read(path)` does not
       | attach the path to the error: the caller is responsible for the
       | context. I would like some lightweight way to control if the file
       | read should include or not the path. The ancestor scanning
       | example from the article is a case where caller context is good
       | IMO, but usually callee context is better for unrecoverable
       | errors. Since it's contextual, having control would be nice.
        
         | jandrewrogers wrote:
         | I've spent a lot of time thinking about error context
         | management in the context of C++. There seems to be a pretty
         | deep conflict between having consistent elegant error context
         | and efficient high-performance code. Explicit delegation works
         | but only sometimes without incurring a runtime overhead. Even
         | trying to find a reasonable and robust middle ground has proven
         | to be elusive.
         | 
         | Expanding on your caller/callee example, sometimes both are far
         | removed from the path, e.g. only having a file descriptor.
         | There are plenty of ways to resolve that file descriptor into a
         | path but one that consistently has zero overhead except in the
         | case where it is required to contextualize an error is not
         | trivial. Being able to resolve outside context implies the
         | management and transmission of context in cases where it is not
         | needed.
         | 
         | I am also coming around to the idea that the error handling
         | context needs to be at least partly co-designed with the
         | logging APIs and internals to have a little more flexibility
         | around when and where things happen. In most software neither
         | of these is really aware of the other.
        
       | sirwhinesalot wrote:
       | Really nice article that covers quite well the complexities
       | involved in dealing with errors. "They're just another kind of
       | value" and "Just crash and restart" are both wrong, error
       | handling is much more complex than that.
       | 
       | Different kinds of errors need to be handled in different ways in
       | various contexts as the article points out.
       | 
       | I won't comment on the error model proposed in the article as I
       | find that without actually trying out stuff like this it's very
       | hard to get a "feel" for it, but at first glance it looks a bit
       | over-engineered.
       | 
       | I think the gist of what makes a good error handling system is as
       | follows:
       | 
       | Certain errors are contract violations/bugs that should not occur
       | in normal program operation, and should not "taint" the code with
       | explicit error handling. The language should provide a mechanism
       | that allows to abort the current "task" when such an error occurs
       | and trigger some error handler. Exceptions are good for this, but
       | it falls on the programmer to define a "task boundary"
       | appropriately. Defensive try/catch all over the place is not
       | that, and is usually done because...
       | 
       | Certain errors are part of normal program operation and the
       | programmer should be reminded to deal with them. Exceptions are
       | terrible for this but Result<T,E> types are great. Having to use
       | Result<T,E> in situations where the error is actually not normal
       | part of program operation (e.g., you're accessing a key on a map
       | that can only be missing due to a bug) is annoying but is easily
       | solved by providing two separate APIs (like Python's
       | __getitem__() and get() methods on dict).
       | 
       | Often times an expected error has no good way to be dealt with at
       | the point it occurs, so there should be an easy way to just abort
       | the current "task" when that happens. This is your ".expect()" or
       | just a giant pile of ? propagation in Rust but Rust panics don't
       | carry enough information by default like you can with an
       | Exception type. Note that is different from the two API version
       | above. You use the "panicking on error" API only in cases where
       | the error is _not expected_ , whereas you use some .expect()
       | equivalent as a way to abort the current task for an _expected_
       | error.
       | 
       | Sometimes you also want to collect multiple errors and abort only
       | after every error is collected, like in a typechecker. This is
       | often done with "NullObject" patterns or "ZII" (most of the time
       | handled very poorly) but there are ways to do it right where the
       | error case "poisons" the data up to the task boundary but cannot
       | propagate further than that (turning into a Result<T,E>
       | equivalent at that point).
       | 
       | The most important part is understanding task boundaries. I find
       | most programs (including my own) do a very poor job of this.
       | 
       | The information contained in the error (sometimes called the
       | context) is very tricky to deal with as what constitutes relevant
       | information for a library or for an app using that library can be
       | very different and there's a performance cost involved with its
       | collection.
        
         | gf000 wrote:
         | Well, arguably Java had the right idea with their
         | checked/unchecked distinction.
         | 
         | I believe effect types (that are the true generalization of
         | checked exceptions) are a pretty good solution to error
         | handling, though as you also write, certain functions where an
         | "error" case is expected should just use ADTs or so (e.g.
         | parsing a string to an int)
        
           | pjmlp wrote:
           | While people love to put this on Java, the idea predates the
           | language by at least two decades, having appeared at least in
           | CLU, Modula-3 and C++, before Java came to be.
        
       | gavinhoward wrote:
       | I searched for conditions and restarts. I did not find them.
       | 
       | That makes me sad because that is what my systems programming
       | language uses.
       | 
       | For context, conditions and restarts come from the Lisp world.
       | They do not unwind the stack. Instead, they run an error handler
       | on error, and if it handles the error, you have the option of
       | restarting the operation that failed.
       | 
       | I have implemented them in C; they are great for systems
       | programming.
        
         | zozbot234 wrote:
         | That's just an overly fancy version of ON ERROR RESUME NEXT.
        
           | gavinhoward wrote:
           | No, it is much more powerful.
           | 
           | ON ERROR RESUME NEXT is equivalent to a catch all in a try
           | catch. Conditions and restarts let calling code set up error
           | handlers, and if one of them handles the error, it would be
           | like starting over from the top of the try block.
        
         | sesm wrote:
         | Did you publish your implementation anywhere? Would be very
         | interesting to see. Thanks!
        
           | gavinhoward wrote:
           | I did, but my Gitea instance stopped working. I am trying to
           | replace it.
        
         | layer8 wrote:
         | You can, of course, do the equivalent with explicit callbacks
         | and a bit of additional boilerplate. However, I have never felt
         | the need to do that (as opposed to, say, having a _numRetries_
         | parameter), so I always wonder how people who advocate for such
         | a mechanism design and modularize their code. It feels like it
         | leads to dependencies on implementation details, as opposed to
         | only relying on a procedure's interface.
        
           | gavinhoward wrote:
           | The errors that a procedure can run into are part of its
           | interface. Languages like Zig and Swift make that explicit.
           | 
           | The difference is that callers (even indirect callers) can
           | register handlers to handle those errors without unwinding
           | the stack.
           | 
           | Maybe the error handler makes it return a default value.
           | Maybe it will deallocate a cache to free up memory in a
           | memory-constrained situation. Maybe it purely ignores EOF or
           | a FILE DOES NOT EXIST error where those do not matter.
           | 
           | I also have a special error handler for parsing. If the error
           | is a parse error, it prints the parse error in standard
           | format, with source file, line, etc. Otherwise, it passes the
           | error on to the next error handler in the chain.
           | 
           | This helps me modularize my code by keeping the parse error
           | concerns separate from error handling for everything else.
        
         | paddy_m wrote:
         | I have read about the CL condition system. I get how restarts
         | are useful for interactive programming.
         | 
         | I don't see how they could be used in most cases where you want
         | a program to run without a programmer intervening. Could you
         | list some more usecases.
        
           | gavinhoward wrote:
           | Restarts don't always need a debugger or intervention.
           | 
           | A classic case would be restarting memory allocation after
           | failure and after the error handler freed cache memory.
           | 
           | Another case would be retrying to open a file when the
           | process has too many files open already. The error handler
           | may close a few that are not critical, then have the restart
           | fire.
           | 
           | Another case is sending something through TCP. Perhaps you
           | try to send something, and it gives you an error. Unbeknownst
           | to you, the message was already sent, but you wait a second
           | or until other connections do less, then restart and try
           | again, and it succeeds. The other end gets a duplicate, but
           | no matter; it's TCP.
           | 
           | Another case is DNS. Say you need to get the IP address for
           | some URL, and you connect to your first default DNS server.
           | However, it happens to be run by your local incompetent
           | sysadmins, and it happens to be down. Your error handler may
           | choose a different, maybe public, DNS server, like Cloudflare
           | or Google, and then restart.
           | 
           | If you think, 'Oh, well, I could program those in without
           | restarts,' you are correct, but the thing is that doing so
           | couples things.
           | 
           | Take the DNS example: if you put that extra error handling
           | logic in the code that actually tries resolving things, then
           | how do you change error handling when you need to?
           | 
           | Let's make the example even more detailed: perhaps you have a
           | fleet of servers, a whole data center. Most of those servers
           | _could_ use a public DNS if they needed to, but perhaps your
           | head node must _NEVER_ use a public DNS for security reasons.
           | The typical way to implement that would mean having an `if`
           | statement for acting differently based on whatever condition
           | would indicate head node or not. That is coupling the DNS
           | resolution with error handling.
           | 
           | But if you have conditions and restarts, then you simply
           | register a different DNS error handler at startup based on if
           | it's the head node or not. Or the error handler could have
           | the `if` statement instead. Either way would decouple DNS
           | resolution from the error handling.
           | 
           | I hope all of that helps.
        
       | CyberDildonics wrote:
       | Is this an "epic treatise" ?
        
       ___________________________________________________________________
       (page generated 2025-03-08 23:00 UTC)