1. 38
  1.  

  2. 17

    In the docs for http.Transport, . . . you can see that a zero value means that the timeout is infinite, so the connections are never closed. Over time the sockets accumulate and you end up running out of file descriptors.

    This is definitely not true. You can only bump against this condition if you don’t drain and close http.Reponse.Body you get from http.Clients, but even then, you’ll hit the default MaxIdleConnsPerHost (2) and connections will cycle.

    Similarly,

    The solution to [nil maps] in not elegant. It’s defensive programming.

    No, it’s providing a constructor for the type. The author acknowledges this, and then states

    nothing prevents the user from initializing their struct with utils.Collections{} and causing a heap of issues down the line

    but in Go it’s normal and expected that the zero value of a type might not be usable.

    I don’t know. They’re not bright spots, but spend more time with the language and these things become clear.

    1. 4

      If you really want to prevent users of your library from using the {} sntax to create new objects instead of using your constructor, you can choose to not export the struct type & instead export an interface that is used as the return value type of the constructor’s function signature.

      1. 10

        You should basically never return interfac values, or export interfaces that will only have one implementation. There are many reasons, but my favourite one is that it needlessly breaks go-to-definition.

        Instead, try to make the zero value meaningful, and if that’s not possible provide a New constructor and document it. That’s common in the standard library so all Go developers are exposed to the pattern early enough.

        1. 2

          Breaking go-to-definition like that is the most annoying thing about Kubernetes libraries.

        2. 4

          That would be pretty nonidiomatic.

          1. 1

            Yea this is a good approach sometimes but the indirection can be confusing.

        3. 9

          Another thing.

          x, err := strconv.ParseInt("not a number", 10, 32)
          // Forget to check err, no warning
          doSomething(x)
          

          This comes up all the time in critiques of the language. Sure, it’s possible. But, like — I’ve never had to catch this in a code review. In practice it just isn’t something that happens. I dunno. I wish Go had a Result type, too. But this class of error is, in my experience, almost purely theoretical.

          1. 3

            I’ve definitely seen this in the wild, both in FOSS projects and at my company. The upside is that popular go linters do catch this, and depending on your editor, this type of check may be enabled by default.

            That said, I much prefer it when a language disallows cases like this than depending on linters.

          2. 10

            It seems like the author is misconstruing things that are operating-as-intended as “quirks”, because it didn’t fit their initial expectations. Much of these points are non-issues for seasoned Go developers

            Edit: For example, the gripe about time.Duration wouldn’t be a problem if the author checked the documentation, which explicitly states:

            A Duration represents the elapsed time between two instants as an int64 nanosecond count […]

            You can’t really blame the language for something if you didn’t read the documentation…

            1. 22

              It seems far too easy to just handwave things away with “it’s documented”; people make tiny mistakes all the time; it’s normal. Minimize the surface area for these kind of tiny mistakes and facilitating easy learning are explicitly among Go’s goals.

              context.WithTimeout() accepts a time.Duration; it’s not unreasonable to expect that it will reject a simple int value. It’s an (arguably silly) mistake, but also he kind that people make all the time, since you can’t remember everything and it’s an easy one to make. All other things being equal, Go would be better and more robust of the typechecker was more strict about this.

              We could, by the way, use your same “read the documentation”-argument against using any type checking at all: “why did you pass an int to this function? It’s clearly documented it only accepts string!”

              1. 10

                Much of these points are non-issues for seasoned Go developers

                What about beginners or people new to the language? To them it may just be, like the author points out, surprising.

                1. 2

                  Just because something is surprising doesn’t make it a quirk

                  1. 7

                    Doesn’t it? I thought that was pretty much the definition.

                    Quirks aren’t a killer and I don’t think the author is arguing against using Go. But if its behaviour is surprising or unexpected to newcomers then that’s something to be aware of and hopefully improve upon.

                2. 1

                  Much of these points are non-issues for seasoned Go developers.

                  Go’s goal is to enable inexperienced developers to churn out code fast. So I think what you are arguing is missing the point on Go’s target demographic.

                  Especially when it comes to time, I think Go does not have a good track record, as described in I want off Mr. Golang’s Wild Ride.

                  1. 2

                    Go’s target demographic, experienced or not, still has to read the documentation even if the features are intended to be easy to learn/use.

                    Quirks are peculiarities or minor departures from the intended effect of a feature; it’s fair to argue some of these even constitute bugs, but I don’t think that’s the case in several of the cases addressed in the article.

                    1. 13

                      You seem to be misinterpreting the word “quirks”. The author is talking about design quirks, meaning parts of Go’s design which, while working as designed, is still surprising or awkward in some way. The most extreme case I can think of right now is probably Javascript’s == operator; it does exactly what it was designed to do, but the consequence is that == isn’t a transitive operator, which is really surprising (a “quirk”).

                    2. 1

                      I don’t think that article supports the conclusion you draw from it.

                      1. 0

                        No, the goal of Go was to fix the issues Google had with large C++ project.

                        1. 2

                          The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt.

                          – Rob Pike

                          1. 4

                            Also Rob Pike:

                            The Go programming language was conceived in late 2007 as an answer to some of the problems we were seeing developing software infrastructure at Google. The computing landscape today is almost unrelated to the environment in which the languages being used, mostly C++, Java, and Python, had been created. The problems introduced by multicore processors, networked systems, massive computation clusters, and the web programming model were being worked around rather than addressed head-on. Moreover, the scale has changed: today’s server programs comprise tens of millions of lines of code, are worked on by hundreds or even thousands of programmers, and are updated literally every day. To make matters worse, build times, even on large compilation clusters, have stretched to many minutes, even hours.

                            Go was designed and developed to make working in this environment more productive. Besides its better-known aspects such as built-in concurrency and garbage collection, Go’s design considerations include rigorous dependency management, the adaptability of software architecture as systems grow, and robustness across the boundaries between components.

                            talk

                            1. 2

                              Where does this quote come from?

                              1. 2

                                That quote is from the talk From Parallel to Concurrent. He says it at 20:40.

                      2. 3

                        This kind of error is even harder to find than if we had just returned nil. Because if we had returned nil, we’d hopefully have a nil pointer panic somewhere down the line.

                        This sort of thing I cannot disagree with more strongly… maybe it’s scar tissue from years of working in Rails but in my opinion there are FAR trickier / more confusing problems resulting from passing nil up the stack than from returning a checkable error and a zero-initialized value like 0, false, and so on.

                        1. 3

                          There is another way of solving this issue using an HTML tag which points to the right git repository, but this requires the Git plaform to support it. It doesn’t feel like a good design decision to require Git platforms to add this special case for Go modules. Not only does it require upstream changes in the Git plaforms, but it also requires the deployed software to be upgraded to a recent version, which is not always a fast process in enterprise deployments.

                          Not a Go programmer but from what I get this doesn’t require any changes from Git platforms: on the contrary one can have a static site with <meta> imports (say, on example.com) that point to your Git platform of choice (say gitlab.example.com).

                          That the <meta> is used and embedded in Git platforms is just a convenience, not a hard requirement.

                          1. 2

                            Yeah, people do this all the time; it’s how e.g. arp242.net/uni resolves to github.com/arp242/uni (or, in the future, perhaps somewhere else). You can just generate these with a little script.

                            1. 1

                              Thanks for the confirmation. Yep, this kind of thing actually makes perfect sense for me - it’s decentralized, makes the package names look good and yet allows redirection at will. Too bad this design is rarely seen in the package manager ecosystem.

                          2. 3

                            Letsencrypt were recently bitten by range - repeatedly processing the first item, not moving to the next. That’s not an obscure quirk, it’s pretty much Go 101, and yet it still happens.

                            1. 1

                              his complaint about using an untyped constant as a time.Duration doesn’t really hold water, because if it was not this way, and it was the way he was expecting, you would never be able to do 5 * time.Second; you would have to do time.Duration(5) * time.Second. Would he ever want to do that? Hmmm….

                              Normally you want to pass this function time.Second * x to timeout in x seconds. The multiplication with time.Second which is of type time.Duration could perform the type cast and make this usage type safe.

                              this is very frustrating, because he’s complaining that he doesn’t like untyped constants because they’re too flexible, but his proposed fix is to … have type casts? What? You either have context.WithTimeout(ctx, 5) and 5 * time.Second or you have to do context.WithTimeout(ctx, time.Duration(5)) and time.Duration(5) * time.Second. Even beyond this, the idea that “I see a function that takes time, and I assume it takes seconds without looking into it, and it didn’t take seconds, so now I think it’s wrong” really makes me think the author is overly difficult to please.

                              1. 1

                                Its is easily doable in most language by overloading operators which results in implicit cast. Or if you can’t, simply use method (time.Second.times(5)).

                                1. 1

                                  there’s no operator overloading in Go, so now you’re proposing multiple language changes.

                                  1. 1

                                    Or if you can’t, simply use methods (time.Second.times(5)).

                                    1. 1

                                      two things:

                                      • an exported function would have to be time.Second.Times
                                      • what is the type of the lexeme 5 containing only the ascii character 5 in that example? If you remove untyped constants, that 5 would only have its default type of int. Which means time.Second.Times would only take a value of type int. Now you’ve got a method time.Second.Times that multiplies a time.Duration with an int to produce a time.Duration, but also, you can multiply two time.Duration values, so now you have … multiplication via the * operator and a Times method. That really doesn’t strike me as being at all preferable to being able to do 5 * time.Second, which you can do right now because of untyped constants.
                                      1. 1

                                        an exported function would have to be time.Second.Times

                                        ok sure.

                                        what is the type of the lexeme 5 containing only the ascii character 5 in that example? If you remove untyped constants, that 5 would only have its default type of int.

                                        Yes, that’s the whole point here.

                                        Which means time.Second.Times would only take a value of type int. Now you’ve got a method time.Second.Times that multiplies a time.Duration with an int to produce a time.Duration.

                                        That’s the idea.

                                        but also, you can multiply two time.Duration values, so now you have … multiplication via the * operator and a Times method.

                                        * operator on time.Duration doesn’t make any sense. What does 5 seconds * 10 minutes results? Why would you even need to do that.

                                        To be fair, multiplication should only be seen as repeated addition, so when you are doing time.Duration * N, this really is time.Duration + time.Duration + ...N, therefor it makes sense to keep N typed as an integer (Or whatever type backing the constant). Same thing where time.Duration / time.Duration = N, I don’t expect N to be time.Duration. So why the language couldn’t type simply type them as so. In the end, typed constant should prevent me from write time.Second + 1 and context.WithTimeout(..., 10).

                                        1. 1

                                          * operator on time.Duration doesn’t make any sense.

                                          that’s -literally- how it works right now, and it means you can compose time.Duration values without needing operator overloading, multiple dispatch, or a bunch of redundant methods. A time.Duration value is just an int64 in nanosecond precision. That’s it. That’s all it is. When you say 5 * time.Second, the 5 is of type time.Duration, so it’s 5 nanoseconds. 5 nanosesconds times 1 second is 5 seconds. Why? Because 1 second is just 1,000,000,000 nanoseconds. A time.Duration value literally just means “a number of nanoseconds”.

                                          What does 5 seconds * 10 minutes results? Why would you even need to do that.

                                          if you wrote that as a constant expression it would fail to compile: https://play.golang.org/p/syX8LYlq15E

                                          if you read the blog post on constants, the Go team does explain why this stuff is the way that it is, and what the problems with the alternatives are. I guarantee you if it did not work this way, people would complain about not being able to do 5 * time.Second, and all the proposed ways of making that work involve many, many changes to the language that are fundamentally at odds with the language’s goals.

                                          1. 1
                                            * operator on time.Duration doesn’t make any sense.
                                            

                                            that’s -literally- how it works right now, and it means you can compose time.Duration values without needing operator overloading, multiple dispatch, or a bunch of redundant methods. A time.Duration value is just an int64 in nanosecond precision. That’s it. That’s all it is. When you say 5 * time.Second, the 5 is of type time.Duration, so it’s 5 nanoseconds. 5 nanosesconds times 1 second is 5 seconds. Why? Because 1 second is just 1,000,000,000 nanoseconds. A time.Duration value literally just means “a number of nanoseconds”.

                                            I understand how thing works and I don’t see how it is relevant to any of my points. “This is the way it works right now” doesn’t bring anything to the table.

                                            What does 5 seconds * 10 minutes results? Why would you even need to do that.
                                            

                                            if you wrote that as a constant expression it would fail to compile: https://play.golang.org/p/syX8LYlq15E

                                            You spend a lot of time pointing irrelevant issue that is irrelevant to the discussion, I suggest you avoid doing that, this at best annoy the other party. The point still stand if you do 1 ns * 10 ns.

                              2. 1

                                The patter of using untyped constants that way is due to a poor coding habit. Constants should be named.

                                1. 2

                                  in the expression fn(5) the untyped constant is 5. You don’t really name every numeric literal in your code before using it, do you?

                                  1. 1

                                    I do, at least for code that will be used in production. So does my entire team.

                                    1. 1

                                      ok so if you wanted to create a value meaning “five second”, you would write this:

                                      five := time.Duration(5)
                                      fiveSeconds = five * time.Second
                                      

                                      oh, no, you can’t do that, because we’ve written the lexeme 5 into our source code. Hmm, that’s “due to poor coding habit”. Hmm.

                                      1. 1

                                        Why not just do fiveSeconds := time.Second * time.Duration(5)?

                                        Or, alternately: secondsUntilFoo := 5 then you can use time.Second * secondsUntilFoo as needed.

                                        1. 1

                                          because in practice if you wanted a value that represented a duration, you would have it of type time.Duration, so you would have something like fooTimeout := 5 * time.Second. Now we’re back to square one. Writing fooTimeout := time.Duration(5) * time.Second is just needlessly verbose.

                                  2. 1

                                    I think there are two things here. First, how idiomatic is the language. For example Python and Go are both idiomatic (Pythonic way / Go way). Second, how much are these idioms being enforced. Python doesn’t enforce idioms at all (it is possible to write C-like Python easily). It seems that in this case, Go enforces “Constants should be named” idiom. However I believe that Go will (or maybe already is) be hacked in a big project (code-base) that escapes from these enforced idioms, since beauty of the code (and everything else) is in its diversity.

                                  3. -1

                                    The main takeaway here seems to be that Go is not some other language and therefore it makes sense to not simply do n the same things you’d do in another language without knowing what you are doing or reading the documentation.

                                    Calling something a quirk because its taking a different approach is somewhat odd. After all what’s the point of there being another programming language when it’s just the same any other?

                                    1. 6

                                      The only criticism which looks vaguely like “Go is not some other language” is the part about error returns where the author wished Go has a Sum type like Rust/Haskell/C++. Literally no other point mention other languages, they just point out places where Go’s design results in confusing or unintuitive behaviour.

                                      1. 1

                                        I think I should have explained better. Go certainly does have quirks. However, the article is talking about design decisions. Since Go is not another language, the decisions here are different. Else we would not need others.

                                        Here an explanation on each of these. They are not meant to defend them and I am not calling the opinion wrong, but would like to point out that they are differentiators that actually make people (like me) pick/like the language.

                                        I’d define a quirk more in the lines of an not explicitly intended side effect and there is not much that seems like it. If we don’t agree on the definition I am sorry, but I only meant to criticize that word as “odd”. Not sure why this comment was voted down as “unkind” otherwise.

                                        Go’s design results in confusing or unintuitive behaviour

                                        That’s exactly what I mean. It’s confusing and unintuitve if you are used to languages with different design choices which are labeled as incorrect, simply because they are different:

                                        If you choose simplicity over correctness you end up cutting corners and delivering broken solutions.

                                        To go a bit more into detail what I mean by saying that design choices are not quirks nor something that makes the language incorrect:

                                        • Zero initialization - that’s something Go actively embraces and in the official guides and talks by the developers you get to learn that idiomatic go has usable zero values, but is uncommon among other languages
                                        • Error returns - you pointed that one out yourself
                                        • Not allowing unused variables - I don’t think that this can be unintuitive or confusing, it’s only different
                                        • Untyped constants - this is certainly going a different way from certain other languages, not even many. It seems very constructed to me also, because you can have the very same thing happen in every code where you don’t specify a unit. Only that in Go it’s very easy to do so and very consistent and simple to use. I’d consider this one especially more intuitively than others. But I think that is highly subjective.

                                        These do not seem to be related to the language itself

                                        • nil slice json - I think that one might even make sense to call a quirk, but it’s not really related to the language itself, but to JSON, how it’s used and JSON in the standard library. From a language perspective having “no data” be “no data” sounds sane and slices in general are unaffected. I would certainly call this one a quirk in the standard library JSON, because it most certainly is not intended.
                                        • Go modules and Gitlab - this is criticizing how module handling/package management works differently. Comparing this with other languages is a bit hard, because there isn’t really some central index software thingy. Again, a different, more decentralized approach. GitLab doesn’t really relate to the Go project at all, so it seems very out of place in an article called “Go quirks”