Sat 13 Jan 2018 — Mon 12 Nov 2018

Go

Designed to be quick to compile, easy to static analyze, and to have a simple type system (no inheritance hierarchy or polymorphism).

Built by Google, Go reflects their particular environment (lots of computers, tonnes of data).

It's also written in reaction to C++ and its flaws.

There's also something special about the dependencies? Not sure.

Has garbage collection. Compared to Java, has a focus on reducing memory allocations and re-using data structures to reduce the amount of garbage to collect.

Has a focus on decent concurrency.

Generally deficient in semicolons;.

Imports and Packages

Declared your package as package packagename.

Import one using import "packagename".

Packages use a Java style namespacing directory hierarchy. However, you don't have to write it all out in your package packagename statement, just the last part.

The main package is your entry point, and should contain a function also called main.

To export things from a package, you need to declare them in the highest scope with a capital letter.

Unused imports are a compile error in Go. So are circular dependencies.

Variable Declarations

Declare variables with var nameA, nameB Type = otherThingA, otherThingB.

You can omit the types if you have something on the right hand side: var nameA, nameB = otherThingA, otherThingB.

You can also omit var entirely and write := instead of =, but only inside a function.

Assign to nothing using _. For example: var _ I = T{} is useful for checking whether a type matches an interface.

If you leave off the right hand side initializer, then your variables will get a default zero value.

nameA := Type()

Constants

Use the const keyword instead. They can't take the := syntax.

Number constants have some magic to do with precision. They'll get the best precision possible at the time you use them.

Types

Instead of inheritance, use composition (the language create argue that inheritance encourages early overdesign).

Types automatically fulfill interfaces if their method signatures match. You don't have to declare this.

Interfaces allow dynamic dispatch.

The only polymorphism in Go is to automatically dereference pointers when you call functions for which that would make the signatures match.

Included Types

bool, string, some number types (signed and unsigned; int, float and complex; different sizes), and runes.

A rune is a unicode code point. It effectively replaced char. Hooray, good choice.

Type Conversions

Type(value) casts value to Type. All type conversions are explicit - missing type conversions are errors.

Type Aliases

For example type MyInt uint8.

Pointers

Pointers have type *SomeType. Get them using & and dereference them using *, just like in C.

You can't do any arithmetic on pointers in Go though.

The purpose of pointers in Go is to let a function mutate its arguments.

Pointers can also be useful to avoid copying large structs. By default, calling a function makes a copy.

Structs

Define a struct:

type SomeStructType struct {
    name SomeOtherType
    name2 SomeOtherType
}

To initialize a struct, pass it values for its fields in order SomeStructType{0, 0}.

Alternatively, you can name the fields SomeStructType{name: 0}.

Omitted fields will get the default zero value.

Then you can just access them with ..

Arrays

Designed to be fast, but a bit more flexible than C.

Make an array by putting [arraySize] before a type, then write the initializer in braces {}. For example: [3]int{0, 1, 2}.

You can slice arrays using [] and :, for example: arr[start:end]. Slices are views onto arrays. You can modify the original through a slice though! Danger, Will Robinson!

You can leave out the start or end of a slice to get all of it in that direction.

If, when constructing an array, you omit the array size, then you will get a slice of that array instead.

You can re-slice a slice. As long as the underlying array is big enough, this is fine.

To see how long an array slice is, use the len function. To see how long its underlying array is, use the cap function.

You can use append to add elements to an array.

Nil

nil means an array slice with no backing array and no start or end.

Alternatively, it means a map with no keys and no backing array.

Maps

Are built in, because it's the new millenium after all.

Make them as:

map[keyType]valueType{
    key: value,
    key: value,
}

There's some special nice syntax where, if you're constructing types as your values, you can leave out their constructor name.

You insert things like most languages: m[key] = value.

Use delete: delete(m, key).

You retrieve elements and check for their presence with a two value assignment: value, hasKey = m[key]. If a value isn't present in the map, it's get the default zero value.

TODO Make

The make function does something to do with dynamic arrays. I need to read the article.

You can also use it make a map: make(map[keyType]ValueType). Not sure why you'd do this.

Functions

Declare with func. Types go after names. Parameter list goes before return list.

func name(paramA, paramB paramABType, paramC paramCType) (returnA, returnB returnType) {
    return this, that
}

The name of a function is optional. You can define them inside other functions.

The type of a function is fn. You can pass them to other functions.

Methods

You can associate a function with a type: func (typeName Type) funcName (argName ArgType) (returnName ReturnType) {}.

This provides a dot syntax for invoking it on the type: Type{}.funcName(arg).

You can only define types in the package where that type was defined.

If a method takes a pointer type (called a pointer receiver), then it can modify the thing it was originally passed.

Pointer receivers can also dispatch on the value (dereferenced) version.

Interfaces

Interfaces declare some methods.

A type meets an interface which it has the methods for.

The empty interface type interface can hold any value.

You can get the original value back out of an interface variable: var t, isT = i.(T).

You can also switch on type. In each of the case statements, your variable will automatically get case to that type:

switch v := i.(type) {
    case T:
        return v
    default:
        return v
}

Some common interfaces are:

  • Stringer
  • Error
  • io.Reader
  • image.Image

Loops

There's just for (no parentheses). It has these semicolon-separated parts:

  • Init
  • Update
  • Termination condition

You can omit any of them.

You can also use the range form. This iterates over an array slice and provides indexes. This is how Go does a foreach loop. It can also loop over maps.

Branching

Go has if. You can put two statements separated by semicolons in the condition. The last one counts.

If you put an extra statement in your if, and it made some variables, then you can also use those variables in the else conditions and blocks.

Go also has switch. It doesn't have fall-through, so you don't need any break statements. It's pretty liberal about what you can use for cases.

You can leave the variable initialization part out. This gives you an alternative syntax for writing a load of if-elseif-elseif statements.

Helper Programs

godoc reads some source code and makes documentation.

gofmt enforces style.

go vet finds likely problems in your code. For example, if you are closing over a loop variable.

go test finds tests.

go get installs a library.

go install installs a program on your system.

Go programs are statically linked to their libraries.

Testing

https://golang.org/pkg/testing/

Make a file with ending with _test.go. go test will automatically find them.

Import and use the testing package.

Write functions like:

TestMyThing(t* testing.T) {
        t.Log("Herp derp.")
        t.Fail("This test failed."); // or t.ErrorF if you want to format in some parameters.
}

Tests in Go continue after failures.

You can output a test coverage report, if you care about such things:

go test -cover -coverprofile=c.out
go tool cover -html=c.out -o coverage.html

Error Handling

The Go creators didn't like error handling in most languages, and so decided to invent something else. There are no exceptions or assertions, and no try/catch/finally block.

To indicate an error, use multi return to provide error codes along with return values.

nil means not an error.

Defer

Go provides defer to handle cleanup that always needs to happen regardless of errors.

You provide a function call to defer. That function call goes on a stack. At the end of the function you wrote defer in, Go executes the stack of deferred functions.

The function args are evaluated straightaway.

You can use defer to modify return values - this is commonly used for fiddling with error codes. This is possible because return values in Go have names, and defer happens after the return keyword.

This seems like a good opportunity for dark magic. It's interesting when you have a self-recursive function…

https://blog.golang.org/defer-panic-and-recover

Panic

Since we don't have exceptions, we need some other mechanism for when our program really has to die.

This is the panic keyword. Every function in the stack will execute its defer stack.

The counterpart to panic is recover(). This is only ever useful inside a deferred function. It returns nil if there is nothing to recover from.

Goroutines

A concurrency primitive based on channels Hoare's Communicating Sequential Processes.

These are basically lightweight threads. They have a stack. The Go runtime will moved them between real threads as resources become available.

You can also do concurrency manually and at a lower level. Normally you might not need to.

go someFunctionCall() starts a goroutine.

Channels

Communicate between gorountines using channels. Make a channel with var ch = make(chan sometype bufferSize).

Send a value to channel with ch <- val. Get one out with:

val, channelOpen := <- ch

Reading a value from a channel blocks it.

A select statement is a bit like a switch statement where you provide multiple possible communication options (reads or writes to channels). It blocks and does the first one which becomes ready. You can provide a default case here, in which case it won't block, but will do the default case immediately if nothing is ready.

close() does the obvious. You don't have to use it. It may be helpful to communicate something though.

Inside a for loop: range works on a channel.

Workspace

This is a directory that contains all your Go source code, compiled binaries and so on. It's set with the GOPATH environment variable.