1. 39
  1.  

  2. 16

    Arenas are exciting! My favourite part of Zig is being able to (idiomatically) choose an allocation strategy depending on what I’m writing. I tend to go with arenas a lot, as they suit long running programs with semi-predictable patterns (games, servers).

    Being able to use a GC language, but with the option to use arenas for performance/memory critical sections… that could be very interesting. (EDIT: I realise that this would require some unidiomatic rewrites, but the interface exposed still looks pleasant to use)

    1. 7

      I thought about mentioning Zig but decided it was too far off topic. Definitely it’s something that makes me excited for arenas.

      1. 3

        I’d have preferred not to bring it up (and let Zig stand on its own, in its own threads, rather than evangelising), but there aren’t that many points of comparison to other languages with nice arena allocators; at least not among those than I write regularly.

      2. 3

        I’m definitely going to give them a run-out in something that has been giving me terrible GC grief - even if it gives just a 15% improvement, that’d be huge.

        1. 3

          I’ve been thinking of writing a minimal (in terms of memory usage) fediverse/activitypub server à la mastodon. I originally wanted to use something very low level like Zig or C (with KCGI), but this has me tempted to just prototype in Go, and then eventually rewrite each request handler to use arena allocation instead of the GC.

          1. 5

            If you start working on that, I can suggest to look (either for using, or for ideas) at my ActivityPub library for Go: go-ap.

            1. 3

              If you like kcgi from kristaps, you might also be interested in honk from tedu.

        2. 14

          Unlike Python, in Go, errors are just values

          Exceptions are values in Python. I’m not sure where the notion that they’re not could come from. This is an issue is flow control. Go has one kind of escape continuation, which is triggered by the return statement, where as Python has two: the one triggered by return and the one triggered by raise. However, both of these handle values.

          1. 4

            I think what it’s traditionally intended by “errors are values in Go” is related to the way they are being handled, not produced. In languages where error escaping is done with exceptions, in this case Python, they are usually handled by type, not by value.

            try:
                raise NameError('HiThere') # raise by value
            except NameError: # handle by type
                print('An exception flew by!') 
                raise
            
            1. 3

              When you use the likes of fmt.Errorf() you’re minting new objects, just as you do with calling an exception type’s constructor. The difference is that you have basic pattern matching (because that’s what an exception handler does) on the type, allowing you to discriminate between them, which you can’t do with the likes of fmt.Errorf().

              1. 3

                OK. I’m not sure if you disagree with me, I just tried to explain how I understood the “errors are values” concept in Go.

            2. 3

              What about panic?

              1. 1

                A panic is more akin to a Unix signal.

                1. 9

                  panic unwinds the stack until a recover — this is not the same as a signal.

              2. 3

                “Errors are just values” links to a blog post that explains what that means. https://go.dev/blog/errors-are-values

                1. 3

                  I know. What I disagree with is the ‘Unlike Python’ bit.

                  1. 6

                    I think you’re looking to closely at specifics, rather than the gist of it: errors are handled using the same language features as other values, unlike exceptions which are handled using dedicated language constructs. Python doesn’t do return Exception even if it could.

                    1. 1

                      This is not accurate. Go tuples are an error-specific language feature in the same as way Python’s except. You can’t use tuples as a general construct.

                      1. 2

                        Go doesn’t have tuples, but it does have multiple return values. Often the last return value is an error, but that’s not a language feature or requirement or anything, it’s just a convention. So I think the OP is accurate.

                        1. 2

                          There’s a strong convention to use it like that, but Go lets you use it with any type you want. You could use it to return e.g. (x,y) coordinates if you wanted.

                          Go dropped the ball on having special-case multiple-value return instead of just single-value return with tuples and destructuring, but having “simple” non-generalizable features is sort of their thing.

                          But even when used with errors, if err != nil is definitely spiritually closer to if (ret != -1) than try/catch.

                          1. 2

                            Go doesn’t have first class tuples, but its return tuples are not specific to error handling. You can return two ints; or an int, a bool, and a float; or whatever else.

                            1. 1

                              Sure, by its nature this is true, because it’s a tuple. But with non-error types you have multiple ways to return them, whereas errors are always returned using the tuple special-case. Tuples exist to return errors.

                              1. 1

                                I’m not sure what you’re saying. As a convention, people return errors as the last type in a tuple, but it’s just a convention. You can return them through global values (like C errno) or out value pointers instead if you wanted to. I have a couple of helper functions that take error pointers and add context to the thing they point at. And people return other things in tuples, like bools or pairs of ints. It’s a historical question whether multiple return was intended for errors, but I do know that before Go 1.0, the error type was a regular type in the os package and it was only promoted to a built in when Roger Peppe (who doesn’t work at Google AFAIK) proposed doing so.

                                1. 1

                                  Out of curiosity, I dug up the original public introduction from 2009. To my surprise, Pike made it entirely through that presentation without ever mentioning error-handling that I can find.

                                  So, I hit the wayback machine. The very first introduction of function tuples on the website uses error returns as the use-case. This slide deck from Pike’s Go course also introduces multiple returns with error handling.

                                  I don’t think it’s fair to say this is just a convention, it is how the designer of the language chose to introduce the feature to people for the first time.

                                  The distinction worth making here is not that “errors are values”, that is uninteresting. It’s true in Python. The distinction is non-local returns vs multivariate functions.

                                  1. 2

                                    You’re describing multiple return values as “function tuples”. I don’t think this is really accurate, as those return values are always discrete. Go doesn’t really have a concept of a tuple.

                                    The thing that “errors are values” tries to communicate isn’t any detail about the specific implementation of the error type, but rather that errors are not fundamentally different than other types like ints or structs or whatever, and that error handling can and should use the same language constructs as normal programming.

                        2. 3

                          In Python, exceptions are values, but they’re not just values; they’re values that interact with the exception-handling mechanism, which does things to and with them (unlike return, which doesn’t care what kind of value you give it, and doesn’t modify that value).

                    2. 1

                      FTA

                      In interface smuggling, the actual implementation is augmented with
                      additional well known APIs, such as io.ReaderFrom and io.WriterTo. Functions
                      that want to work more efficiently when possible, such as io.Copy(), attempt
                      to convert the io.Reader or io.Writer they obtained to the relevant API and
                      then use it if the conversion succeeded:
                      
                          if wt, ok := src.(WriterTo); ok {
                             return wt.WriteTo(dst)
                          }
                          if rt, ok := dst.(ReaderFrom); ok {
                             return rt.ReadFrom(src)
                          }
                      

                      This is more commonly described as the “interface upgrade” pattern, which is unfortunately totally non-viable as a general-purpose design approach, for reasons which are IMO best illustrated by felixge/httpsnoop.

                      The lesson of that package is that it’s basically infeasible to expect any stdlib interface like io.Reader or io.Writer to be able to be “upgraded” to any other more-specific expression of that interface. It’s really unfortunate that this isn’t well understood by the core team. The Unwrap method in the proposal improves but does not solve the underlying problem.

                      1. 1

                        It’s definitely a problem prone pattern. I also think it’s worth distinguishing performance optimizations like ReaderFrom and new capabilities like Push. (It usually doesn’t make sense to wrap a performance thing.) But I think it’s less problematic if you can use a wrapper and have an explicit error marker.

                        1. 1

                          What’s a better way of doing it?

                          1. 1

                            A function that takes an interface like io.Reader as an input parameter can try to upgrade that param to something more specific and with more capabilities, but that upgrade is at best optimistic and can’t be assumed to be successful. In general there is no way for this kind of upgrade to be written in a way that can be verified by the compiler.

                            Code that has different behavior for an input param of type io.Writer vs. io.WriterTo vs. etc. etc. should define different methods for each of those types.