proj-plbook-plChGoLang

Difference between revision 119 and current revision

No diff available.

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:

Attributes:

Pros:

Cons:

Tours and tutorials:

Feature lists and discussions and feature tutorials:

Overviews:

Best practices:

Library highlights:

Respected exemplar code:

Core types (from https://tour.golang.org/basics/11 ):

Process:

Retrospectives:

" 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