1. 12
  1. 29

    I might have sort of agreed if recover() didn’t exist. Since it does exist, panic(), it’s essentially a design where exceptions do exist, but you cannot differentiate between them, and you can also use them with goroutines.

    I also agree that signalling errors in the return values is usually a better approach, but Go also got the Either a b/'a, 'b result type laughably wrong. In the design where product types exist but sum types and destructuring don’t, nothing keeps you from just ignoring the possibility of an error and using the alleged return value in an expression. If you are given a Maybe or Either type, you have to explicitly handle the case when the callee gives you an error value instead of a result.

    And then, practical implementations of algebraic effects seem like a matter of time now.

    1. 8

      I think you need recover() to scope “it’s game over man” to something that makes more sense; for example the current goroutine or current HTTP request. In those scenarios, completely bringing down a server with a panic() doesn’t strike me as desirable, but completely bringing down the current connection or goroutine does.

      Unfortunately this does allow for the possibility of abuse and using panic() as poor man’s exceptions – something not uncommon for new Go programmers more used to exception-based languages (I did this myself) – but that’s not the intended use. I’m not sure if there’s any way around this?

      1. 3

        Oh, I’m not against having a mechanism for signalling a failure from goroutines (there has to be some). My point is that, at the language level, goroutines that are expected to cause recoverable panics are indistinguishable from those that aren’t supposed to panic at all or should always be treated like fatal errors if they do.

        It’s the same problem as now knowing whether a function throws exceptions or not, viewed from a slightly different angle. If you want your program to decide what to do with panicking goroutines (retry/abort/ignore ;), you’ll end up replicating either exceptions or sum types in a compiler-unenforceable way.

        1. 1

          Fair enough. I’ve actually made the mistake by not scoping panics() a few times, and making a more explicit choice about this would probably be better. In practice, I’ve actually found that most – although obviously not all – panics() should actually be scoped to the goroutine.

        2. 1

          What’s a valid reason to panic in a HTTP handler? Most of my experience has been with poorly written dependencies that panic, which I now tend to avoid. But I also can’t really recall choosing to panic in a web server in code I’ve written.

          1. 2

            In general: you probably shouldn’t. But you don’t always control panics: they may happen due to nil dereference, accessing an array beyond its size, etc. In practice, it’s pretty hard to give a hard guarantee that any given non-trivial Go code will never panic.

            1. 1

              The idea is that the server shouldn’t die just because one request triggered a panic.

        3. 26

          Hm, a language that uses product types instead of sum types to represent “result or error” is certainly not doing exceptions right.

          1. 6

            Exactly

            If a function returns a value and an error, then you can’t assume anything about the value until you’ve inspected the error.

            You can still use the returned value and ignore the error. I’m not calling that “right”

            1. 2

              That is intentional and useful. If the same error has different consequences when called by different callers, the error value should be ignored. However, when writing idiomatic Go, you generally return a zero value result for most error scenarios.

              Assigning _ to an error (or ignoring the return values altogether) is definitely something that could be trivially caught with a linter.

              1. 1

                I still think it’s a mistake to allow both to coexist. In this case, you need an additional tool to catch something. Whereas with proper sum types, it is simply not possible to access a return value if you get an error. What you do with that is up to you.

                val, err := foo()
                if err != nil { /* log and carry on, or ignore it altogether */ }
                bar(val) // <- nothing prevent you from doing that, and it very likely a mistake
                
            2. 5

              Thought the same. It gets this right:

              Go solves the exception problem by not having exceptions.

              And here it goes wrong:

              Instead Go allows functions to return an error type in addition to a result via its support for multiple return values.

              1. 4

                Another case of “Go should have been SML + channels”, really. That would have been as simple, cleaner, and less error prone. But what can you expect from people who think the only flaw of C is that it doesn’t have a GC…

                1. 1

                  Not just GC but also bounds checking.

                  1. 1

                    That’s so obvious that I forgot about it. I don’t know of any modern language that doesn’t have a (bound-checked) array type. On the other hand they decided to keep null nil…

                    1. 1

                      You have to keep in mind that C is most certainly not a modern language & they were intentionally starting with C & changing only what they felt would add a lot of value without adding complexity.

                      1. 1

                        For sure, I just don’t get why anyone would want that. 🤷 I guess.

                        1. 2

                          Primarily linguistic simplicity in the service of making code easier to read.

              2. 1

                Website is down :(

                1. 1

                  Probably paniced.

                  It is up for me at time of writing, but in any case here’s an archive.today mirror.

                2. 1

                  leading to patterns like

                  catch (e Exception) { // ignore }

                  It’s rather an anti-pattern rather than “pattern” (for example static analysis will mark this as a warning). Also, the e Exception should be Exception e.

                  RuntimeExceptions & errors are like panics (well-written code should never throw them at runtime) while regular exceptions are something that you have to handle (like Go’s return types) and something you can’t anticipate (e.g. I/O exception during file reads cannot be avoided by defensive programming).