Table of Contents for Programming Languages: a survey
Go (golang)
Because it is moderately well-known and well-liked, Go (also called Golang) gets its own chapter.
Good for:
- writing internet servers (todo cite). efficient, concurrency, statically-linked executable.
Attributes:
- Compiled
- Garbage-collection
Pros:
- Compiles fast
- Simple grammar
- "Compared to other languages in the C family, its grammar is modest in size, with only 25 keywords (C99 has 37; C++11 has 84; the numbers continue to grow). More important, the grammar is regular and therefore easy to parse (mostly; there are a couple of quirks we might have fixed but didn't discover early enough)." -- https://talks.golang.org/2012/splash.article
- "Unlike C and Java and especially C++, Go can be parsed without type information or a symbol table; there is no type-specific context" -- https://talks.golang.org/2012/splash.article
- Separate compilation (todo)
- Gofmt
- Produces a single statically-linked executable.
- Structurally typed interfaces
- Relatively efficient memory usage compared to many high-level languages
- named return values [1]
- M:N mapped goroutines (by 'M:N mapped' i mean that Go can start multiple threads and migrate goroutines among them so that if a goroutine blocks, the other goroutines aren't blocked; this is in contrast to having all Goroutines on one thread, or to 1:1 mapping (where each goroutine would have its own OS thread))
- these are semi-premptive; the programmer doesn't have to explicitly yield, rather the Go runtime will yield at various places, such as function calls; but it is possible to go into a tight loop in which case the Go runtime never yields
Cons:
- Lack of fine-grained control over memory management
- Bills itself as a 'systems programming language' however does not allow the programmer fine-grained control over memory management, instead providing mandatory garbage collection. Many commentators who use C++ instead of Go say that the lack of fine-grained control over memory management is what is keeping them from using Go instead of C++, and prevent Go from being a true 'systems programming language' (see various comments at https://news.ycombinator.com/item?id=6417859 ).
- No generics
- No dynamic linking
- No exceptions (more of a best practice than a language constraint though)
- Compiler error to have unused variables and imports -- this can make it cumbersome to experiment and debug, as you have to comment out unused stuff
- No scheduler preemption as of this writing (Aug 2013), but they're working on it
- Multiprocess Go programs can use data races to gain access to unsafe pointer manipulation, meaning that sandboxed systems (such as App Engine) must restrict their users to single-threaded Go. See http://research.swtch.com/gorace
Tours and tutorials:
Feature lists and discussions and feature tutorials:
Overviews:
Best practices:
Library highlights:
Respected exemplar code:
- the Go standard library [2]
Core types (from https://tour.golang.org/basics/11 ):
- bool
- string
- int int8 int16 int32 int64
- uint uint8 uint16 uint32 uint64 uintptr
- float32 float64
- complex64 complex128
Process:
Retrospectives:
- https://golang.org/doc/faq
- http://golang.org/doc/ sections Articles, Talks
- http://blog.golang.org/
- http://talks.golang.org/2012/splash.article
- http://dotgo.sourcegraph.com/post/99652962343/brad-fitzpatrick-on-the-future-of-the-go-programming
- http://research.swtch.com/generic (a blog post that may shed some light on why Golang doesnt have generics)
- Why Go doesn't have Generics (HN comment by Russ Cox): "The Go team is not against generics per se, only against doing things that are not well understood and/or don't work well with Go. There are deep technical issues that must be solved to fit the idea of generics into Go in a way that works well with the rest of the system, and we don't have solutions to those. I wrote on my blog about one issue years ago (http://research.swtch.com/generic), but there are others too. Even supposing you get past the problem on that page, the next thing you would run into is how to allow programmers to omit type annotations in a useful, easy-to-explain way. As an example, C++ lets you write make_pair(1, "foo") instead of make_pair<int, string>(1, "foo"), but the logic behind inferring the annotations takes pages and pages of specification, which doesn't make for a particularly understandable programming model, nor something the compiler can easily explain when things go wrong. And then there's a princess in another castle after that one I am sure. We have spoken to a few true experts in Java generics and each of them has said roughly the same thing: be very careful, it's not as easy as it looks, and you're stuck with all the mistakes you make. As a demonstration, skim through most of http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.ht... and see how long before you start to think "was this really the best way to do this?" (For example, http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypePa..., but note that the latter page is only one part of the FAQ, not the entire FAQ.). To be very clear, we acknowledge this fact: there are definite disadvantages to not having generics. You either use interface{} and give up compile-time checking or you write code generators and complicate your build process. But there are also definite disadvantages to generics as implemented in existing languages, and there is a very large advantage to not compromising today: it makes adopting a better solution tomorrow that much easier. As I said in the interview at http://www.pl-enthusiast.net/2015/03/25/interview-with-gos-russ-cox-and-sameer-ajmani/ : "Go is more an engineering project than a pure research project. Like most engineering, it is fundamentally conservative, using ideas that are proven and well understood and will work well together. The research literature’s influence comes mainly through experience with its application in earlier languages. For example, the experience with CSP applied in a handful of earlier languages—Promela, Squeak, Newsqueak, Alef, Limbo, even Concurrent ML—was just as crucial as Hoare’s original paper to bringing that idea to practice in Go. Programming language researchers are sometimes disappointed that Go hasn’t picked up more of the recent ideas from the literature, but those ideas simply haven’t had time to pass through the filter of practical experience." I believe generics is one of those ideas. It certainly needs at least one more iteration, possibly a few more than that. "
- https://blog.golang.org/open-source
- https://github.com/golang/go/wiki/ExperienceReports
- GopherCon 2014 Opening Keynote by Rob Pike
- How Go Was Made
- The Evolution of Go
- https://groups.google.com/g/golang-nuts/c/6dKNSN0M_kg/m/Y1yDJRwQBgAJ
- "Another aspect of Go being a useful programming environment is having well-defined semantics for the most common programming mistakes, which aids both understandability and debugging. This idea is hardly new. Quoting Tony Hoare again, this time from his 1972 “Quality of Software” checklist: As well as being very simple to use, a software program must be very difficult to misuse; it must be kind to programming errors, giving clear indication of their occurrence, and never becoming unpredictable in its effects. The common sense of having well-defined semantics for buggy programs is not as common as one might expect. In C/C++, undefined behavior has evolved into a kind of compiler writer's carte blanche to turn slightly buggy programs into very differently buggy programs, in ever more interesting ways. Go does not take this approach: there is no “undefined behavior.” In particular, bugs like null pointer dereferences, integer overflow, and unintentional infinite loops all have defined semantics in Go. " -- [3]
- "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" via [4]
- "It must be familiar, roughly C-like. Programmers working at Google are early in their careers and are most familiar with procedural languages, particularly from the C family. The need to get programmers productive quickly in a new language means that the language cannot be too radical. – Rob Pike" via [5]
- https://cacm.acm.org/magazines/2022/5/260357-the-go-programming-language-and-environment/fulltext
- "When we first announced Go we called it a systems programming language, and I slightly regret that because a lot of people assumed that meant it was an operating systems writing language. And what we should have called it was a 'server writing language', which is what we really thought of it as." -- Rob Pike via https://video.ch9.ms/ch9/6596/ed57ccc9-2cbb-42b0-b4f0-70909f486596/LangNext2014PanelSystemsProgramming.mp4 via https://news.ycombinator.com/item?id=31440369
" I made a list of significant simplifications in Go over C and C++:
regular syntax (don't need a symbol table to parse)
garbage collection (only)
no header files
explicit dependencies
no circular dependencies
constants are just numbers
int and int32 are distinct types
letter case sets visibility
methods for any type (no classes)
no subtype inheritance (no subclasses)
package-level initialization and well-defined order of initialization
files compiled together in a package
package-level globals presented in any order
no arithmetic conversions (constants help)
interfaces are implicit (no "implements" declaration)
embedding (no promotion to superclass)
methods are declared as functions (no special location)
methods are just functions
interfaces are just methods (no data)
methods match by name only (not by type)
no constructors or destructors
postincrement and postdecrement are statements, not expressions
no preincrement or predecrement
assignment is not an expression
evaluation order defined in assignment, function call (no "sequence point")
no pointer arithmetic
memory is always zeroed
legal to take address of local variable
no "this" in methods
segmented stacks
no const or other type annotations
no templates
no exceptions
builtin string, slice, map
array bounds checking
...
We also added some things that were not in C or C++, like slices and maps, composite literals, expressions at the top level of the file (which is a huge thing that mostly goes unremarked), reflection, garbage collection, and so on. Concurrency, too, naturally.
One thing that is conspicuously absent is of course a type hierarchy. -- " [6]
" Doug McIlroy?, the eventual inventor of Unix pipes, wrote in 1964 (!):
We should have some ways of coupling programs like garden hose--screw in another segment when it becomes necessary to massage data in another way. This is the way of IO also.
That is the way of Go also. Go takes that idea and pushes it very far. It is a language of composition and coupling.
The obvious example is the way interfaces give us the composition of components. It doesn't matter what that thing is, if it implements method M I can just drop it in here.
Another important example is how concurrency gives us the composition of independently executing computations.
And there's even an unusual (and very simple) form of type composition: embedding.
These compositional techniques are what give Go its flavor, which is profoundly different from the flavor of C++ or Java programs. " [7]
Spec
https://golang.org/ref/spec
Features
defer, panic, recover
defer is somewhat similar to 'finally' in languages with try/catch/finally, but one important difference is that the scope of 'defer' is a function, whereas the scope of a 'finally' is a try/catch block, and in those languages there can be many try/catch blocks in one function; so Golang is trying to force you to write small functions that only do one thing [8]. Otoh, the 'finally' in try/catch/finally must be in the same lexical scope as the 'try', whereas the 'defer' can appear in any lexical scope, but its body will not be run until the end of the current function scope. This means that multiple 'defer's may be encountered before any of their bodies is run. In this case, the bodies of the defers are placed onto a LIFO stack. An example, from [9]: this function prints "3210":
func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
This enables many instances of a resource to be acquired within an inner scope, and then not disposed of until the end of the function. This is unlike try/catch/finally or Python's 'with', in which the disposal must be done in the same scope in which the resource was acquired.
Links:
Iota for enumerated constants
OOP
Golang claims not to have "classes", however you can define methods on any type declared in your package [10], including struct types [11]. You cannot declare methods on a type from another package [12] (todo: what is the rationale for that?).
" Go is “OO-ish” with its use of interfaces — interfaces are basically duck typing for your structs (as well as other types, because, well, just because). I had some trouble at first understanding how to get going with interfaces and pointers. You can write methods that act on WhateverYouWant? — and an interface is just an assertion that WhateverYouWant? has methods for X, Y, and Z. It wasn’t really clear to me whether methods should be acting on values or pointers. Go sort of leaves you to your own devices here.
At first I wrote my methods on the values, which seemed like the normal, American thing to do. The problem of course is that when passed to methods, values are copies of the original data, so you can’t do any OO-style mutations on the data. So instead methods should operate on the pointers, right?
This is where things get a little bit tricky if you’re accustomed to subclassing. If your operate on pointers, then your interface applies to the pointer, not to the struct (value). So if in Java you had a Car with RaceCar? and GetawayCar? as subclasses, in Go you’ll have an interface Car — which is implemented not by RaceCar? and GetawayCar?, but instead by their pointers RaceCar?* and GetawayCar?*.
This creates some friction when you’re trying to manage your car collection. For example, if you want an array with values of type Car, you need an array of pointers, which means you have to need separate storage for the actual RaceCar? and GetawayCar? values, either on the stack with a temporary variable or on the heap with calls to new. The design of interfaces is consistent, and I generally like it, but it had me scratching my head for a while as I got up to speed with all the pointers to my expensive and dangerous automobiles. " [13]
Structural typing / duck typing in interfaces
" jerf 4 hours ago
"Take interfaces. In Java you might start with them. In Go - in the best case - they emerge, when it's time for them."
And it's a relatively subtle language feature that does this, the way that any struct that implements a given interface automatically conforms to that interface without having to be declared. Which means you can declare an interface that foreign packages already conform to, and then freely use them. Which means that you can code freely with concrete structs to start with, and then trivially drop in interfaces to your code later, without having to go modify any other packages, which you may not even own.
I completely agree that on paper Java and Go look virtually identical. But the compounding effect of all those little differences makes them substantially different to work with. Good Go code does not look like good Java code. You'd never confuse them.
reply
kasey_junk 3 hours ago
There is a big downside to structural typing as golang implements it though. Refactoring tools. They quite simply cannot do the same kinds of safe refactoring something like a Java refactoring tool can do, because you can't be sure if the function you are trying to rename, add parameter too, etc. is actually the same function in question.
There are times when I love the structural typing aspects of golang (trivial dependency inversion) and there are times when I hate it (nontrivial renames), its one of many trade-offs you have to be prepared for in golang.
reply " [14]
" pcwalton 3 hours ago
> Which means you can declare an interface that foreign packages already conform to, and then freely use them.
But you cannot do the reverse: you cannot make a type from a foreign package conform to your interface by adding new methods to it. This is because, with structural typing, it's not safe to add methods to types in other packages, since if packages B and C both were to add a conflicting method Foo to a type from package A, B and C could not be linked together. This is a major downside of structural typing for interfaces. Swift, for example, has the "extension" feature, and Java's design allows for it in principle, but it's fundamentally incompatible with Go's design.
reply " [15]
"
justinsaccount 3 hours ago
> But you cannot do the reverse: you cannot make a type from a foreign package conform to your interface by adding new methods to it.
I thought you could.
You don't add the methods directly to it, but you can easily embed the foreign type into a new type that confirms to the interface you want.
type FooWrapper struct {
Foo
}
func (fw FooWrapper) SomeFunc() {
...
}
reply
pcwalton 2 hours ago
That's making a new type, which causes a lot of friction. For example, a []Foo array is not castable to a []FooWrapper? array without recreating the entire thing.
reply "
[16]
Opinions
- https://boyter.org/posts/my-personal-complaints-about-golang/
- http://cowlark.com/2009-11-15-go/
- https://www.quora.com/Do-you-feel-that-golang-is-ugly/answer/Tikhon-Jelvis?srid=hMkC&share=1 feels that Golang is ugly, due to error handling (returning both a return value and an error code, returning returnVal, err not as a tuple but in an irregular way, having nullable types instead of option/maybe types, having nullable types almost everywhere but not in primitive types, particularly not having strings be nullable even though arrays are, having a default/zero value for primitive types but not for user-defined types, not having full type inference, not having generics, maps having special-case generics, not having generics leading to a need to cast to interface{} frequently and give up static typing. bt
- -- "People like it because it's fast, the type system gets out of the way, it's not as weird as the lisp-like languages for those used to C syntax, has some cool modern features (from closures, to easy concurrency) and has a big and current standard library. I also happen like the language on its merits." -- [17]
- "fundamentally broken by a couple of ... decisions the designers made: no exceptions, no parametric polymorphism." -- [18]
- https://news.ycombinator.com/item?id=9257626
- "The creators of Go seem to think that simplicity is achieved by removing everything that isn't totally necessary. I'm not sure if I agree philosophically. If I do, then I'm not sure I like that kind of simplicity for writing software. I'd rather have slightly DRYer code than slightly simpler code." -- [19]
- Channels are not enough
- "Golang is not an especially impressive programming language --- as a language. But it seems to me like it is an undeniably impressive programming tool...The compiles are instantaneous...Go's primary goal is: excellent tooling." -- [20], [21]
- " I think the author gets closest to the mark in the Conclusion, but still falls short. Go is very attractive as an "upgrade" from Ruby/Python or Java. It's a good replacement for the interpreted languages when speed/performance matters and it makes the async paradigm feel much more accessible. And compared to Java, the fact that Go compiles to a native binary is a huge benefit. It's not a replacement for C or a good "teaching" language, nor would anyone call it "mature" at this point, but it does fill a niche and fills it pretty well." -- [22]
- "Golang was explicitly engineered to thrive in projects built by large groups of programmers with different skill levels" -- [23]
- "it is a language for teams." -- [24]
- "The goals of the Go project were to eliminate the slowness and clumsiness of software development at Google, and thereby to make the process more productive and scalable. The language was designed by and for people who write—and read and debug and maintain—large software systems" -- [25]
- "While it has great features like compilation speed or easy concurrency, the main feature that makes Go special is its extreme simplicity." -- [26]
- "I appreciate Go because it's fast, small (re: syntax and standard library), garbage collected with nice primitives (pointers and slices), concurrent with high-level primitives (goroutines, channels, select), expressive enough (strings, maps, range, first-class functions, type system, etc.), first-class support for Unicode, and it has a rock-solid standard library." -- [27]
- "I think error handling and documentation of the errors returned by methods could be improved in Go. I dislike having to comb through the Go standard library source to see what types of errors a method returns." -- [28]
- "...two reasons I like Go: 1) It's concurrency model was a revelation compared to anything I did in the C world. 2) It's opinionated....In Go, there's only one way to format your program correctly, there's only one way to document your program correctly, there's only one recommended way to serialize to JSON, it only has one (IMO really awesome package management system) etc. It's remarkable how little thought I now need to put into those things." -- [29]
- toread: https://news.ycombinator.com/item?id=10413861
- problems with errors-are-values (as opposed to exceptions): "Go's simplicity is initially refreshing, then a huge pain once you find yourself writing the same thing over and over again. A good example is errors being values — which is a great idea. But then you realize every single function needs to be littered 1-10 cases of "if err != nil {return nil, err}". It's an extremely common pattern. It's tiring to write, over and over. Tiring to refactor, too: If you change a signature (to add an error, or add another return value, for example), every error check has to be updated. Unfortunately, Go doesn't offer any abstractions that might allow you to avoid such boilerplate. Functions that return errors cannot ever be chained, for example: If you have "func bar() (MyStruct?, error)", you cannot do "foo(bar())". You must always assign the result to an intermediate variable. You could embed the error in the MyStruct? struct instead, but that goes against Go's grain." -- [30] "Chaining does work, but obviously the function needs the same signature as the return values, which limits things since most APIs don't accept errors as input." -- [31]
- "To be fair, I love many aspects of Go: Compilation speed, relative performance, ease of concurrency, static strictness. But after working with Go for a while and being quite productive with it, I'm at the same time seriously pining for a better language to replace it." -- [32]
- "On one hand, it addresses many of the pain points I've experienced with other languages. It's easy to build and deploy, reasonably performant, and has a powerful and consistent standard library. On the other… developing in it feels like a total slog. It manages to be simultaneously far too anal and overly forgiving about syntax. Visibility definition using upper/lowercase is a shite idea. Package names (and packaging generally) are a mess. Magical built-in globals are a huge design smell. The lack of generics, as cliché a complaint as it is, results in loads of duplicated or generated code when building anything more complex than the simplest app. And so on. I find the whole experience of using it to be unpleasant (rather like using a blunt knife), and yet it fits a set of requirements that mean I keep developing new projects using it – particularly high-performance small services that do things like fling JSON around, for which the built-in HTTP libraries + Gin are brilliant." -- [33]
- "Go checks a lot of boxes for my ideal language for developing web services: Static type, C derived, has garbage collection, generates a single binary, supports concurrency very well, is opinionated, is small/simple, its community prefers to just use standard lib for most work, etc. Yes, Generics is an issue and so is debugging. But, overall, I can't think of many other options that check so many boxes." [34]
- Q: "Could you give an example of a program you think Go isn't suited to? " A: "The compiler was straightforward to express in Go, but I wouldn't want to write a static analysis tool in it. I don't especially love writing database-backed web applications in Go." [35]
- "It's really quite a pity that Go's channel syntax treats channels as unique snowflakes, rather just being sugar for calls into an interface that would allow the underlying channel implementation to differ based on software needs." "That's an excellent example (in a long list) of things that would be possible with generics, or even parameterized packages. They could have provided an interface Channel[T] with syntax sugar if desirable. But as it is, everything in Go that can handle multiple types has snowflake status." [36] [37]
- "Go doesn’t provide a way to have arbitrarily sized buffers - you have to allocate the buffer size in advance...Not having arbitrarily buffered channels means that a naive send on any channel could block at any time. You want to send on a channel and update some other bookkeeping under a mutex? Careful! Your channel send might block!" [38]
- If you haven’t yet, please go take a look at...What Color is Your Function. Without being about Go specifically, this blog post...lays out exactly why goroutines are Go’s best feature (and incidentally one of the ways Go is better than Rust for some applications). If you’re still writing code in a programming language that forces keywords like yield on you to get high performance, concurrency, or an event-driven model, you are living in the past.... Go is ...one of the best...of languages that implement an M:N threading model that’s not 1:1, and ...that’s powerful....If I had to pick one other leading feature of Go, it’s interfaces. Statically-typed duck typing makes extending and working with your own or someone else’s project so fun and amazing..." [39]
- http://yager.io/programming/go.html
- " Having just spent the last two months writing Go code, exceptions are the thing I miss most (well, besides the ternary operator and map/reduce operations). Not only are errors painful to debug without stacktraces, but every single method call is followed by three lines of "if err != null {". I am amazed that folks tolerate the sheer amount of repetitive typing required by the language. " [40]