Effective Go is a howto on writing idiomatic Go.

Toolchain

GOPATH

It is convention for all Go code to be hosted in a single workspace, and GOPATH points to the root of this workspace.

A Go workspace contains one or more project repositories. Each repository is independently version controlled.

Inject this into ~/.profile and include the $GOPATH/bin on PATH:

export GOPATH=$HOME/go
export PATH="$PATH:$GOPATH/bin"

Using go env validate GOPATH:

cd `go env GOPATH`

Run, build and install

Source is placed within the src dir within $GOPATH like $GOPATH/src/github.com/bm4cs/cool-app/main.go

To just run a program, without building a binary:

$ go run main.go
hello world

To build a statically linked binary:

$ go build
$ file demo
demo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked

Cross compiling is available too:

$ GOOS=windows go build
$ file demo.exe
demo.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

The binary can be written to $GOPATH/bin (or $GOPATH/pkg for libraries) which is normally on the PATH:

$ go install
$ ls $GOPATH/bin | grep demo
demo

Dependencies

TODO: modules

$ go get github.com/golang/example/hello
$ $GOPATH/bin/hello
Hello, Go examples!

Formatting

Format a file to stdout:

$ gofmt main.go

Diff:

$ gofmt -d main.go

Update (write) the original source file:

$ gofmt -w main.go

Documentation

Local CLI help:

$ go doc fmt Printf

func Printf(format string, a ...interface{}) (n int, err error)
Printf formats according to a format specifier and writes to standard
output. It returns the number of bytes written and any write error
encountered.

Local documentation HTTP server:

$ godoc -http :7070

Structuring a source tree

project-layout curates common conventions followed by the community.

Check out go-structure-examples for flat, layered, modular and DDD based layouts.

The lang

Variables

There’s no such thing as a undefined variable in Go. Each data type has a well defined zero value.

var a int //set to 0
var b int = 10
var d, e, f bool
var (
    g int
    h string
    i int = 1234
    j, k, l bool
)

Inside a func can use short variable declaration (a declaration and assignment in one step):

m := 1
n, o := 2, 3

Assignments:

e, f = f, e
a := 11
a, p := 100, 200

The blank identifer (for ignoring outputs):

a, _ := f()
g(a)

Control structures (if, switch and for)

if

Go conditions uniquely supports an initialisation statement:

if err := f(x); err != nil {
    return err
}

Ternary operators are not supported, due to poor readability.

switch

Case statements do not fallthrough in Go.

Supports vanilla style:

var model string
//...
switch model {
    case "dell":
        // Do something
    case "acer":
        // Do something else
    case "compaq":
        // Do something different
    default:
        // Do nothing
}

However, unlike many langs Go supports another switch-expressionless form:

var velocity int
//...
switch {
    case velocity == 0 || velocity == 1:
        //...
    case velocity >= 10:
        //...
    case f(velocity) >= f(100):
        //...
}

If fallthrough is required there are a couple of options:

case velocity >= 10:
    //...
    fallthrough

Or pile up the case statements with multiple expressions:

switch motion {
    case "walk", "run":

for

The for keyword covers off all loop types in Go. No support for while, do, until loops exist.

  • For loop for i=0; i<10; i++
  • While loop for i<10
  • Infinite loop for
  • Enumerable iteration for i, v := range s

I/O

fmt

The fmt (fumpt) package does formatted I/O.

  • parameters are separated by spaces by default
  • it can handle a variety of input types
  • explicit argument indexes (one-based) are supported fmt.Sprintf("%[2]d %[1]d\n", 11, 22)

Supported format verbs are like C, but simpler.

  • %v value in default format
  • %#v print the value in valid Go syntax, handy for dumping structs or slices
  • %t boolean
  • %d base 10
  • %b base 2
  • %x base 16
  • %s raw string
  • %T type of the value
r, g, b := 124, 87, 3

// ...as #7c5703  (specifying hex format, fixed width, and leading zeroes)
fmt.Printf("#%02x%02x%02x\n", r, g, b)

// ...as rgb(124, 87, 3)
fmt.Printf("rgb(%d, %d, %d)\n", r, g, b)

// ...as rgb(124, 087, 003) (specifying fixed width and leading zeroes)
fmt.Printf("rgb(%03d, %03d, %03d)\n", r, g, b)

// ...as rgb(48%, 34%, 1%) (specifying a literal percent sign)
fmt.Printf("rgb(%d%%, %d%%, %d%%)\n", r*100/255, g*100/255, b*100/255)

// Print the type of r.
fmt.Printf("%T\n", r)

Reading input done with fmt.Scan, fmt.Scanln and fmt.Scanf

var s string
var n int
cnt, err := fmt.Scan(&s, &n)
fmt.Println(cnt, s, n, err)

Scanf will parse input precisely as the format string specifies (in the snippet below: an ‘a’ character, followed by a space, a string, a newline, then an integer):

fmt.Scanf("a %s\n%d", &label, &age)

Fuller example:

var n1, n2, n3, n4 int
var f1 float64

// Scan the card number.
str1 := "Card number: 1234 5678 0123 4567"
_, err := fmt.Sscanf(str1, "Card number: %d %d %d %d", &n1, &n2, &n3, &n4)
if err != nil {
    fmt.Println(err)
}
fmt.Printf("%04d %04d %04d %04d\n", n1, n2, n3, n4)

// Scan the numeric values into a floating-point variable, and an integer.
str2 := "Brightness is 50.5% (hex #7ffff)"
_, err = fmt.Sscanf(str2, "Brightness is %f%% (hex #%x)", &f1, &n1)
if err != nil {
    fmt.Println(err)
}
fmt.Println(f1, n1)

CLI

Args

  • complete command line is available via an array named os.Args
  • use len() to size os.Args
  • iterate over individual args with range like for _, e := range os.Args

Flags

The flag package is a standard package for parsing CLI flags ./coolprog -verbose -count=7

It works by creating a pointer for each flag. Flags are then evaluated with flag.Parse().

verbose := flag.Bool("verbose", false, "Print verbose log outputs")
count := flag.Int("count", 1, "Number of items to collect")
flag.Parse()
fmt.Printf("verbose = %t, count = %d\n", *verbose, *count)

When working with the flag pointers, they need to be dereferenced with the * syntax.

By declaring variables explicitly can avoid the need to deref pointers:

var max int
flag.IntVar(&max, "max", 1, "The maximum number of shares to buy on a triggered event")

Unfortunatly flag does not support POSIX style flags (i.e both short and long forms -n --name). Community packages such as pflag support this.

Basic Data Types

Type conversion

Many langs support type casting. Go provides conversion functions.

var u uint = 64
var u64 unit64 = uint64(u)

When truncation is needed, go always rounds down towards zero.

Strings

  • the strings package is where many string utility functions live for working with bytes (strings.IndexByte) and runes (strings.IndexRune)
  • the strconv package provides many conversions (such as Atoi, Itoa, FormatInt, ParseBool, FormatFloat, ParseFloat)
  • the unicode package offers many identification and conversion utilities such as IsSpace or ToTitle exist.
  • they are immutable (mutability is possible using a slice of bytes)
  • treated as sequences of bytes with arbitrary values, even a null byte, differentiating them from C strings
  • substrings s[1:4] would return characters his in the below string (the second index hits the first byte after the substring)
  • substring shortcuts s[:4] and s[4:]

Sample string:

| T | h | i | s |   |
  ^   ^   ^   ^   ^
  0   1   2   3   4

Unicode

Go has first class UTF-8 support. UTF-8 is a brilliant variable length encoding invented by Ken Thompson of UNIX and C fame.

Go uses UTF-8 to encode unicode. Recall UTF-8 is a variable length encoding (i.e. it can use 1-byte or upto 4-bytes).

fmt.Println(len("a")) // 1
fmt.Println(len("ä")) // 2
fmt.Println(len("走")) // 3

We can see the UTF-8 encoding in action here, with simple English characters using only a single byte, with the more esoteric characters using 2 or more bytes depending how deep into the unicode listing they fall.

Go allows you to treat strings at both the byte and character level.

Runes

At the character level, the unit of a character is called a rune. A rune is just an alias for a int32.

Runes are presented by range when iterating over a string:

for i, e := range "abä走." {
    fmt.Println("range:", i, e, string(e))
}

// range: 0 97 a
// range: 1 98 b
// range: 2 228 ä
// range: 4 36208 走
// range: 7 46 .

Strings can be worked with at both the byte and rune levels. UTF-8 generally makes this work out fine:

fmt.Println("IndexByte:", strings.IndexByte("abä走.", '.')) // IndexByte: 7
fmt.Println("IndexRune:", strings.IndexRune("abä走.", '走')) // IndexRune: 4

The unicode/utf8 package provides rune aware functionality, such as RuneCountInString.

str := "Hello, 世界"
fmt.Println("bytes =", len(str)) // bytes = 13
fmt.Println("runes =", utf8.RuneCountInString(str)) // runes = 9

Showcase of some unicode and strings functionality:

import (
	"fmt"
	"os"
	"strings"
	"unicode"
)

func acronym(s string) (acr string) {
	afterSpace := false

	for i, e := range s {
		if (afterSpace || i == 0) && unicode.IsUpper(e) {
			acr += string(e)
		}
		afterSpace = unicode.IsSpace(e)
	}

	return acr
}

func main() {
	s := "Pan Galactic Gargle Blaster"
	if len(os.Args) > 1 {
		s = strings.Join(os.Args, " ")
	}
	fmt.Println(acronym(s))
}

String literals

  • the usual \t tabs, \n newline
  • \x61 is a raw byte in hex, evaluating to the character a
  • \142 is a raw byte in octal
  • multi-line literals are supported using backticks

Numbers

  • The bool type is strictly handled by go, and no auto-type conversion is supported (e.g. if 1).
  • Conditions can evaluate a boolean type, a comparison operation or a bool func.

Integers

Go supports integers explicitly; their signage and size.

Signed Unsigned Bytes
int8 uint8 1
int16 uint16 2
int32 uint32 4
int64 uint64 8

Several aliases to these types exist:

  • byte is a uint8
  • rune is a int32
  • int and uint are CPU platform specific

Arithmetic overflows are silently discarded.

var a uint8 = 250
a = a + 10 // result is larger than 256, so a ends up being 4

Bitwise operations

As the usuals, such as a & b for bitwise AND, a >> n right shift by n bits, a &^ b for a AND NOT b (aka bit clear, all the bits in b set to 1 will be cleared in the result), etc.

// var a uint8 = 64
// uint8 is 8-bits wide, and looks like this at the bit level:
// | 256 | 128 |  64 |  32 |  16 |  8 |  4 |  2 |  1 |  0 |
// |   0 |   0 |   1 |   0 |   0 |  0 |  0 |  0 |  0 |  0 |
//
// bit shifting to the right 1 bit
// a = a >> 1
// | 256 | 128 |  64 |  32 |  16 |  8 |  4 |  2 |  1 |  0 |
// |   0 |   0 |   0 |   1 |   0 |  0 |  0 |  0 |  0 |  0 |

var a uint8 = 64
a = a >> 1 //32

Floating point

  • Supports IEEE 754 real numbers with the float32 and float64 types.
  • Prefer float64
  • Decimal fractions cannot be accurately modelled using the binary system. Never use when accuracy is needed, such as for money calculations.

The float binary problem:

$ bc
bc 1.07.1
obase=2
scale=40
1/10
.0001100110011001100110011001100110011001100110011001100110011001100\
110011001100110011001100110011001100110011001100110011001100110011

Constants

Go supports typed and untyped constants, in either the singular or multiple forms.

Go constants can represent these types:

  • A unicode rune, '😀' '\377' '\u266B' '\U0001F600'
  • Integers, as decimal 1337, octal 01337, or hex 0x1CAFE42
  • Floats 123.456e7, 123e20, 1.234, .71828, 2.e7, .5e-10
  • Strings, any sequence of unicode runes or escape sequences, "A string \U0001F600"
  • Booleans true or false
  • The iota keyword is used to generate enumerations (starting at 0 and incrementing by 1)

Enumerations are possible with the iota keyword. Reassigning iota in the same list of consts has no effect.

const kilo = 1024
const pi32 float32 = 3.1415926535897932384

const (
    cint = 299792458
    ctitle = "Theo de Raadt created OpenBSD in 1995 by forking NetBSD"
)


// all constants are set to 12
const (
    twelve = 12
    dozen
    months
)

const (
    zero = iota
    one
    two
    three
    four
)

// iota calculation
const (
    ten = iota * 10 + 10
    twenty
    thirty
)


// iota bit shifting
const (
    read    = 1 << iota  // 0001
    write                // 0010
    execute              // 0100
    isLink               // 1000
)

Pointers

  • Supports C like *type pointer type, & address of, and *p pointer indirection
  • Pointers have a default value of nil
  • Its illegal to point to an arbitrary (literal) address, or a constant
  • When nil, pointer indirection causes a panic
  • Can compare pointer addresses p1 == p2, or the values they point to *p1 == *p2
  • The built-in new() allocates variable and returns a pointer to it p := new(int)
var a int = 1337
var p *int = nil

p = &a
fmt.Println("p's value is a's address", p)
fmt.Println("p's value yields a's value", *p)

Result:

p's value yields a's value 1337
p's value is a's address 0xc0000b6010

The new() operator returns a pointer to an un-named variable:

p := new(int32)
*p = 64

Functions

  • Functions are first class objects in Go (i.e. can be assigned to a variable)
  • Subsequent parameters of the same type can be grouped func f(n, m int, s string)
  • Named parameters are NOT supported (i.e. must pass in the order specified)
  • Variadic functions are supported func f(s ...string)
  • Functions can return multiple values (types must be enclosed in pathentheses)
  • Return values can be named, which get declared as variables within the scope of the function

Grouped parameters types

func f(n, m int, s string) {
    fmt.Println("A function with grouped parameters:", n, m, s)
}

Variadic functions

func f(s ...string) {
    for _, str := range s {
        fmt.Print(str + " ")
    }
    fmt.Println()
}
func main() {
    f("clear", "is", "better", "than", "clever")
}

Multiple return values

func f() (int, string, error) {
    return 0, "ok", nil
}

n, s, err := f()

Named return values

func f() (n int, s string, err error) {
    n = 5
    s = "high five!"
    return // Don't do this - it works, but it can be confusing
}

Recursion

func factorial(n int) int {
    if n == 0 {
        return 1
    }
    return n * factorial(n - 1)
}

Deferred functional calls

Using defer will “queue” a function call until the point where the calling function itself exits.

This is particularly useful in simplifying a function that has multiple exit points, needing to perform the same resource cleanup (e.g. f.Close()) at all return points.

Instead, just ensure it gets done once at the functions end with defer.

func f() error {
    f, err := os.Open("foo.go")
    if err != nil {
        return err
    }
    defer f.Close()
    fmt.Println(f.Name())
    if f.Name() == "foo.go" {
        return nil
    }
    // More code, maybe more exit points
    return nil
}

Functions as values

Functions being first class objects, means you can throw them around like variables.

Note the funcVar declaration below, there is no need to name parameters.

func f1(s string) bool {
    return len(s) > 0
}

func f2(s string) bool {
    return len(s) < 4
}

var funcVar func(string) bool

func main() {
    funcVar = f1
    fmt.Println(funcVar("abcd"))
    funcVar = f2
    fmt.Println(funcVar("abcd"))
}

Function literals (anonymous functions)

funcVar = func(s string) bool {
    return len(s) > 4
}

Its possible to evaluate the function literal after defining it, which can be useful when creating goroutines in a loop:

var result string = func() string {
    return "abcd"
}()

Passing functions to functions

As first class objects, in addition to being treated as variables, this translates over to function parameters in the same way.

func f1(s string) bool {
    return len(s) > 0
}

func f2(s string) bool {
    return len(s) < 4
}

func funcAsParam(s string, f func(string) bool) bool {
    return f(s + "abcd")
}

func main() {
    fmt.Println(funcAsParam("abcd", f1))
}

Closures

When a function literal is defined within another function.

Closures get access to the local variables (i.e. the call stack) of the outer function, even after the lifetime of the outer function.

func newClosure() func() {
    var a int
    return func() {
        fmt.Println(a)
        a++
    }
}

c := newClosure()
c()
c()
c()

Error Handling

  • Unlike other langs, exceptions are NOT supported.
  • Go takes a different standpoint, error handling is instead part of normal control flow logic
  • Expected errors are simply returned to the caller (Go functions can return multiple things, its convention for error to be the last return value of functions)
  • Caller should always check each returned error before moving forward
  • Error handling strategies: propagate, retry, log and continue, or log and exit
  • A panic unlike an error, are used to communicate exceptional errors or serious failures
  • A panic results in a controlled crash

Error handling strategies

Propagate to caller

  • while the raw error can simply be returned, its best practice to wrap a new error message prior to propagating
  • avoid newline characters in messages for grep-ability.
func propagate(i int) error {
    if err := verify(i); err != nil {
        return fmt.Errorf("propagate: %s", err)
    }
    return nil
}

Retry

For transient errors, like a failing network link.

  • wait a time period before retrying
  • try a different port, file name, IP, reduced size, etc
  • fallback to default values
func retry(i int) error {
    err := propagate(i)
    if err != nil {
        err = propagate(i / 2)
        if err != nil {
            return fmt.Errorf("retry: %s", err)
        }
    }
    return nil
}

Log and continue

The error is no significant enough to disturb control flow. Log it and move on.

func onlyLog(i int) {
    if err := retry(i); err != nil {
        log.Println("onlyLog:", err)
    }
}

Log and exit

The error is a show stopper. The log package conveniently has log.Fatal(), log.Fatalln() and log.Fatalf(), which print and exit.


pkg/errors

pkg/errors makes it easy to account for each error and its context, as errors continue to be propagated and wrapped up the call chain. By providing a simple API errors.Wrap() to wrap errors with call stack context before propagating, errors.WithStack() to print full list of error messages

_, err := ioutil.ReadAll(r)
if err != nil {
        return errors.Wrap(err, "read failed")
}

Panic and Recover

Pull the ejector seat; something unpredictable happened or the caller passed invalid arguments or calling context.

func unexpectedError(p *int) {
    if p == nil {
        panic("p must not be nil")
    }
}

func main() {
    unexpectedError(nil)
}

Packages and Libraries

Go’s namespace concept.

  • The main package represents an executable
  • Any other package represents a library
  • A library can contain functions, types, variables, but NO main function
  • Libraries are a great way to break up complexity into chunks
  • They are imported import "mastergolib/dice". File names of imports are irrelevant.
  • A simple visibility rule applies for package, a function/variable/type that starts with an uppercase letter is visible to outside users of the package.
  • If a package needs some initialisation logic to fire at startup, use the init() hook.
func init() {
    rand.Seed(time.Now().UnixNano())
}

Package aliases

  • Occassionally you will need to use libraries with same name, and alias can help un-ambiguise this
  • Nice way to cut down long package names
import "time"
import tm "github.com/someuser/time"

Imported unused packages for side effects

import (
    "database/sql"
    _ "github.com/mattn/go-sqlite3"
)

Inspecting a package API

Say you’ve just pulled down a nice new package to mess with go get github.com/appliedgocourses/bank.

Use go doc to inspect the API like so (getting more specific with path details, if you have multiple packages with the same name).

To get an API overview:

go doc bank
go doc appliedgocourses/bank
go doc github.com/appliedgocourses/bank

To drill down to a specific function:

go doc bank.Balance

Advanced Data Types

Arrays

  • As expected, are fixed in size and type.
  • Unusually arrays in Go are treated as value types (i.e. not by reference). Full copies are made when assigning to another variable, or passing to a function.
  • Use array pointers (or slices) to overcome this expensive for large arrays.
  • For this reason, in Go, it is more idiomatic to use slices

In Go, the type always goes on the end:

var name [10]string
var size [1024]int

The contents of the array can be immediately assigned (composite literal):

// set each element
a := [...]int{1, 2, 3, 4, 5}

// selectively set elements by index
a := [10]string{0: "First", 9: "Last"}

Accessing array elements:

i := 10
a := [10]string{0: "First", 9: "Last"}
b := a[3]
fmt.Printf("%#v\n", a[i]) // runtime panic
// fmt.Printf("%#v\n", a[10]) // compile time error

Iterating with range, which return the current index and value. Remember range returns a COPY of each element. If you want to mutate the array, use the index (not the value):

list := [...]int{1, 2, 3}
for i := range list {
    list[i] = 4
    fmt.Println(list[i])
}
fmt.Println(list)

Array pointers, do NOT need to be dereferenced:

pa := &[3]bool{true, false, true}
b := (*pa)[0]   // no need to dereference
b := pa[0]      // a vanilla array value expression just works

Comparing arrays, works if the type it holds is comparable:

a1 := [3]string{"one", "two", "three"}
a2 := [3]string{"one", "two", "three"}
fmt.Println("a1 == a2:", a1 == a2)

2D arrays (matrices):

var matrix [4][4]int
for i := range matrix {
        for j := range matrix[i] {
        matrix[i][j] = (i + 1) * (j + 1)
    }
}
fmt.Println(matrix)

Slices

Unique to Go, slices are more versatile than arrays and happen to use arrays internally.

  • Unlike arrays are reference based (i.e. use pointers to the underlying array) and can even be extended! 💥
  • Use Go built-in functions such as len(), cap(), append() copy()
  • As a result are cheaper to pass around
  • Multiple slices can overlap with each other, all pointing to the same underlying array (i.e. mutating one slice can affect another)
  • Internally a slices have a header that defines:
    • current length (as shown by len())
    • remaining capacity in the internal array (as shown by cap())
    • pointer to the data in the internal array
  • Slices can be based of unnamed arrays created with make()

!IMPORTANT!

Please grok the following about slices, or risk insanity:

  • Go’s pass by value semantics does NOT support deep copying.
  • The means when you assign a slice variable to another, or pass a slice into a function call, Go will (shallow) copy it.
  • The 3 components (len, cap and address) of the slice header will be copied to another slice header.
  • The internal array used for the slices however will not be copied, and all copied slices will continue point to the same array in memory.

Examples help amirite?

  • Once upon a time there was a slice a := []int{0, 0, 0, 0}
  • Internally for a Go made this slice header &{Data:824633819968 Len:4 Cap:4}
  • You innocently pass a into a function func appendOne(s []int) { ...
  • Because Go does NOT do pass by reference, Go will create a (shallow) copy of a for s.
  • Now slice s has its own slice header, copied from a’s slice header i.e. &{Data:824633819968 Len:4 Cap:4}
  • Now penny drop moment.
  • Notice the pointer address to the internal array of the slice. Go lazily just kept the same address of a’s array, instead of copying the internal array of a to a new memory location, and setting the pointer address to this new array for s.
  • If it did do the later, this is known as a DEEP COPY.
  • Go does NOT do deep copies.
  • Now if you set out and start updating the slices a and s, even though they are copies, their internal array pointers still point to the same memory.
  • In other words they will update the same data.

This can get even more wacky:

  • The slices copied from the same origin can drift apart. For example, if you extend slice a beyond its capacity, Go will reallocate it’s internal array to a new array double the size, and update its slice header with the new size, capacity and pointer to the new larger array.
  • However slice s will continue to point to the old smaller array memory location.
  • HOLY MOLY. This could create some wacky bugs.
a := [8]int{}   // dumb array
sa := a[3:7]    // slice
sb := a[:4]     // slice, from first element to index
sc := a[4:]     // slice, from index to last element

Makes a slice from array a, from element 3 up to, but not including, element 7. Weird, like strings. Use the boundary trick to help remember this oddity.

Extending slices

Option 1: re-slice the slice

a := [8]int{}
s := a[3:6]    // len(s) == 3, cap(s) == 5
s = s[:1]      // len(s) == 2, cap(s) == 5
s = s[:cap(s)] // len(s) == 5, cap(s) == 5

Option 2: append the slice

Append arbitrary new elements to slice, even beyond its original capacity. Go will allocate a brand new array twice the size, if capacity is exceeded.

Dag alert: append() returns a new slice due to Go’s pass by value semantics.

s = append(s, 1, 2, 3, 4)

Creating a slice with make

A slice definition looks similar to an array, except no size is specified:

var s []int   // a slice (not an array!)

Instantiating a slice (3 options):

// option 1: slice literals
s = []int{}
s = []int{1, 2, 3}

// option 2: make
s := make([]int, 10, 100)

// option 3: append() a nil slice
append(s, 1, 2, 3)

Byte slices

A most popular slice type, as they can be used as mutable strings.

  • Casting between strings and byte slices is easy.
  • Slices can mutate data in they underlying array, diverging from Go’s usual pass by value semantics.
  • range on byte slices does not recognise Unicode runes (i.e. each piece of data is treated as an individual byte)
  • Byte slices can’t be initialised with a literal e.g. var coolString []byte = "smells like teen spirit" //compile error
  • Instead, type casting must be used e.g. var coolString []byte = []byte("smells like teen spirit")
  • The built-in bytes package offers a ton a byte slice, almost mirroring what can be done with the strings package

Casting examples:

s := "Eat 200 grams of protein daily"
b := []byte(s)
s = string(b)

Maps

The classic key/value based data structure.

  • They are mutable, and offering all the CRUD operations.
  • As with slices, Go’s pass by value semantics does NOT support deep copying. See !IMPORTANT! in the slices section above.
  • Maps internally point to an internal hash table data structure (the same way slices point to an internal array)
  • All keys must be of the same type (ex: string), and must be comparible ==.
  • All values must be of the same type (ex: int)
  • Elements of a map are not externally addressable, so no pointers to individual elements.
  • Although Go does provide a Set type (a data structure where every value is unique), maps can easily simulate one var set map[string]struct{}. An empty custom structure

Creating maps

By default is nil:

var m map[string]bool
fmt.Println("m is nil:", m == nil) // true

To initialise, two options, use make() or a composite literal.

Using make():

m := make(map[string]int)
m := make(map[string]int, 100)  // pre-allocate space for 100 elements

Using a composite literal:

moons := map[string]int{
    "Earth": 1,
    "Mars": 2,
    "Jupiter": 67,
    "Saturn": 62,
}

CRUD (create retrieve update delete) operations with maps

Creating:

moons := map[string]int{
    "Earth": 1,
    "Mars": 2,
    "Jupiter": 67,
    "Saturn": 62,
}
moons["Uranus"] = 27
moons["Neptune"] = 14
moons["Pluto"] = 5

Retrieving:

mercuryMoons := moons["Mercury"]
fmt.Println("Mercury has", mercuryMoons, "moons.")

To test existance, use the comma, ok idiom:

_, ok := moons["Venus"]
if !ok {
    fmt.Println("Data for Venus does not exist")
}

Iterate with range of course:

for k, v := range moons {
    fmt.Println("Planet", k, "has", v, "moons.")
}

Updating:

moons["Jupiter"] = moons["Jupiter"] - 1
moons["Jupiter"]++

Deleting:

delete(moons, "Pluto") // Pluto is not a regular planet

Named types (user defined types)

Your own types based on other primitive types.

  • Defined using type keyword
  • Can cast into other types, if they are of the same underlying type
  • Its possible to add custom methods to these types
  • Print types using fmt and %T
  • Functions, like primitive types, can be with named types
type km float64
type miles float64

func howLongDoINeedToWalk(distance km) {
    fmt.Println("You need to walk", distance/5.0*60, "minutes for", distance, "km.")
}

func main() {
    var dst km = 12
    howLongDoINeedToWalk(dst)
}

Function named type

type action func(int) int

func double(n int) int {
    return n *2
}

func apply(change action, n []int) {
    for i := range n {
        n[i] = change(n[i])
    }
}

func main() {
    ints := []int{1,2,3,4,5}
    apply(double, ints)
    fmt.Println(ints)
}

Type aliases

Tells Go to bind an identifier to a given type.

Great way for libraries to insulate external users of concrete types.

type FooType = oldlib.FooType

Struct

A way of structuring data together.

  • Is a data type, like a named type, declared using the type keyword
  • The same visibility rules of packages apply (i.e. if field identifier starts with upper or lower character)
  • Are comparable, if each field type is in-turn comparable (earth == jupiter)
  • Can use dot accessors immediately on functions that return a struct ex: uppercase(mars).Name
type Planet struct {
    Name             string
    Mass             int64
    Diameter         int
    Gravity          float64
    RotationPeriod   time.Duration
    HasAtmosphere    bool
    HasMagneticField bool
    Satellites       []string
    next, previous   *Planet
}

var earth, jupiter Planet

Supports initialisation with a literals.

  • Make sure to always set the final trailing comma when using multiline syntax
  • Specific fields can be left out (they will get their default values)
  • Field names can be omitted entirely. In this case, all values must be included in the same order as they are declared
mars := Planet{
    Name:           "Mars",
    Diameter:       6792,
    RotationPeriod: 24.7 * 60 * time.Minute,
    HasAtmosphere:  true,
    Satellites:     []string{"Phobos", "Deimos"},
    Mass:           642e15, // in millon metric tons (1t == 1000kg)
    previous:       &earth,
    next:           &jupiter, // Remember the final comma
}

Struct embedding

Yes, structs can be embedded within one another.

  • Embedding offers a lightweight inheritance. In OOP inheritance is a way of sharing behavior between classes of objects.
  • When a struct is embedded into another struct, the outer struct has access to the methods of the embedded struct!
type CelestialBody struct {
    Name           string
    Mass           int64
    Diameter       int64
}

type Planet struct {
    CelestialBody    // Anonymous field: No name, only a type
    HasAtmosphere    bool
    HasMagneticField bool
    Satellites       []string
    next, previous   *Planet
}

var p Planet
p.Name = "Venus"

Field tags

A string literal that can be added to a struct field.

  • For marking up metadata about fields
  • Popular use-cases include code generators, ORM’s, serializers.
  • For example, the encoding/json package, when marshalling data, processes all field tags starting with json:, allowing field renamings to occur
  • Hot tip: wrapping field tags in backticks, saves having to escape special printable characters such as double quotes
type weatherData struct {
    LocationName string `json:"location"`
    Weather      string `json:"weather"`
    Temperature  int    `json:"temp"`
    Celsius      bool   `json:"celsius,omitempty"`
    TempForecast []int  `json:"temp_forecast"`
}

Struct methods

In Go, functions can be bound to types, commonly known as methods in OOP.

  • A method, similar to a function, but uniquely defines a receiver data type, ex: func (p Person) Age() int {
  • The receiver is a typed parameter that is put in front of the function name, within parentheses
  • The type of the receiver is must be a base type (i.e. NOT an instance of a pointer or interface) that exists within the same package
  • Like the rest of Go, pass-by-value semantics apply to method receivers.
  • As a result, its NOT possible to update a receiver value directly, a pointer receiver must be used.
  • If you want to base a type on a struct that has methods ex: type Contact Person, methods will not flow to Contact, as they have a receiver type of Person. Bummer.
  • Instead, use type embedding or type aliases (type Contact = Person).
type Person struct {
    Name        string    // exported
    dateOfBirth time.Time // internal
}

func (p Person) Age() int {
    return int(time.Since(p.dateOfBirth).Hours() / 24 / 365)
                          ^-- accessing the receiver
}

Pointer receivers, allow methods to mutate state in the object (ex: a setter method):

func (p *Person) ChangeName(name string) {
    p.Name = name // p.Name is a shorthand for (*p).Name
}

Receiver Method Sets

All the methods for a type (T) that have a matching receiver type.

type Counter int

func (c *Counter) Up() {
    (*c)++
}

func (c *Counter) Reset() {
    *c = 0
}

func (c Counter) Get() int {
    return int(c)
}

In the above, the method set for Counter includes just the Get() method. The Up() and Reset() methods are of a pointer receiver type (*Counter), and therefore are not part of Counter method set.

  • The method set of a type T consists of all methods with a receiver type T
  • The method set of a type *T consists of all methods with a receiver type *T plus all methods with a receiver type of T.

Interfaces

  • Defines one or more function signatures, but does NOT implement them
  • In Go, a type implicitly (automatically) satisfies an interface by implementing all the functions defined by an interface
  • Yes Java peeps, there is NO need to explicitly declare that an interface is implemented.
  • Be careful when using interfaces with types that have methods with pointer receivers
  • The compiler will stop you, with Invitation does not implement Helloer (Hello method has pointer receiver)
  • In h = Invitation { event: "hackathon" }, h is of type Invitation, whose method set does NOT include any methods for *Invitation (pointer receiver).
  • Quick fix is easy, just assign the interface to a pointer of the type h = &Invitation{event: "birthday party"}
  • Interfaces can build upon other interfaces, such as the ReaderWriter interface from the standard library.
  • All types in Go satisfy the empty interface interface {}, can be handy in the absence of generics.
  • Other cool standard lib examples include the bufio package, error which is just an interface, the sort.Interface.

Two concrete Helloer implementations:

type Helloer interface {
    Hello(string)
}

type Greeting string
func (g Greeting) Hello(name string) {
    fmt.Println(g+",", name)
}

type Invitation struct {
	event string
}
func (inv Invitation) Hello(name string) {
	fmt.Printf("Welcome to my %s, %s! Please on come in\n", inv.event, name)
}

func Main() {
    var h Helloer

    h = Greeting("G'day")
    h.Hello("Benjamin")

	h = Invitation { event: "hackathon" }
	h.Hello("Benjamin")
}

Interfaces support:

  • Decoupling concrete implementations from consumers
  • Mocking out implementation for unit testing
  • Changing complete implementations over time

For example, the below doSomething could take in a net.Conn, an *os.File* or a *strings.Reader, all of which implement the interface of io.Reader.

func doSomething(in io.Reader) {
}

The List type below implements the sort.Interface contract, giving it sort powers:

type List []string
var names List
func (l List) Len() int {
    return len(l)
}
func (l List) Less(i, j int) bool {
    return len(l[i]) < len(l[j])
}
func (l List) Swap(i, j int) {
    l[i], l[j] = l[j], l[i]
}

func main() {
    list := List{"really really long", "short", "quite long", "longer"}
    sort.Sort(list)
    fmt.Printf("%#v\n", list)
}

Interface internals

To represent an interface, Go stores its type descriptor (the concrete type that implements the interface) and value descriptor (a pointer to the concrete instance).

For example:

  • Declaring a unassigned interface var w io.Writer, its type is nil, its value is nil
  • Assigning it to an instance that satisfies the contact w = io.Stdout, the type is set to *os.File, its value set to the memory location of its instance 0xdeadbeef

WARNING - dont assign nil variables to interfaces

Take the following:

var w io.Writer
var f *os.File
w = f

This will set the type descriptor of w to *os.File, but its value descriptor to nil.

Attempting to nil test the interface will pass, however using it will fail.

Maddening times.

Type assertion

Turns a looser type into its concrete type. If the assertion fails, it will panic.

func doSomething(in io.Reader) {
    conn, ok := in.(net.Conn)
    if ok {
        fmt.Println("Read from remote address", conn.RemoteAddr())
    }
}

Type switches

Exposes underlying type:

switch stream := in.(type) {
    case net.Conn:
        fmt.Println("Read from remote address", stream.RemoteAddr())
    case *os.File:
        fmt.Println("Read from file", stream.Name())
    case *strings.Reader:
        fmt.Println("Read from a string of length", stream.Len())
}

Cool cool cool

stdlib

The Go standard library is a thing of beauty, pragmatic and brilliant documentation.

Gems:

  • gob streams for exchanging binary between an Encoder and Decoder. Handy for sending or receiving objects to a remote machine or a blob store.

Tools

  • wuzz TUI REST client

Make

A Good Makefile for Go

Vim setup

  1. Install the vim-go and coc.nvim plugins. I use neovim with vimplug to do this.
  2. Setup GOPATH and GOBIN env.
  3. Run vim +GoInstallBinaries to install supporting toolchain in GOBIN.
  4. Setup coc config by running :CocConfig within vim. Ensure the language server is setup for go, using the provided snippet.
  5. Jump into some go code such as vmware-tanzu/velero, and test gd goto def, and ctrl o to bounce back. Cursor over some func and hit :GoDoc to show contextual docs.
  6. Congrats, you have tight integration with go toolchain thanks to vim-go, but also modern language intelligence thanks to LSP, gopls and coc-nvim.

Libraries

Data

  • BoltDB transactional key-value store, both as byte slices. Organised in buckets.

Middleware

  • negroni idiomatic HTTP middleware
  • alice painless middleware chaining

Web

  • gin HTTP web framework
  • go-jose implements the Javascript Object Signing and Encryption set of standards JWE, JWS and JWT (JSON Web Encryption, JSON Web Signature and JSON Web Token)
  • gorilla a general purpose web toolkit that solves doing context, routing, RPC over HTTP, strong typing forms to structs, secure cookies, session and websockets.