Skip to the content.

Go notes

next step in tour of go: Exercise: Fibonacci closure

Language Overview

Install on Ubuntu

sudo snap install go

editor: Atom - go-plus

Go Syntax

Documentation

Package builtin documents the builtin functions and identifiers

imports

two formats:

import "package1"
import "package2"
// the "factored" import statement - use this
import (
  "package1"
  "package2"
)

exported names

a name is exported if it begins with a capital letter

multiple results

a function can return any number of results:

func swap(x, y string) (string, string) {
	return y, x
}

named return values

return values may be named. If so, they are treated as variables defined at the top of the function. These names should be used to document the meaning of the return values.

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return // a naked return -> returns the named return values. Don't use unless in short functions.
}

variables

declare variables with the var keyword. A var statement can be at package or function level:

var isOk, isError bool

variables with initializers

if an initializer is present the type can be omitted:

var c, python, java = true, false, "no!"

short variable declarations :=

inside a function, the := short assignment statement can be used in place of a var declaration with implicit type.

c := true

basic types

example with “factored” format (similar to import) and exported names

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

Zero values

Variables declared without an explicit initial value are given their zero value.

The zero value is:

Type conversions

The expression T(v) converts the value v to the type T.

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

Type inference

Print the type of a variable:

func main() {
	v := 42.0 // change me!
	fmt.Printf("v is of type %T\n", v)
}

Constants

Declared with the const keyword.

Loop - for

Without parenthesis. Note usage of := (short assignment statement).

for i := 0; i < 10; i++ {
	sum += i
}

Loop - for is while

C’s while is spelled for in Go. Basically a for loop without initializer and post statement.

sum := 1
for sum < 1000 {
	sum += sum
}

Loop - forever

Infinite loop.

for {
  // do something
}

If - with a short statement

The parentheses in if are optional.

Like for, the if statement can start with a short statement to execute before the condition. Variables declared by the statement are only in scope until the end of the if and any else.

if v := math.Pow(x, n); v < lim {
	return v
}

Switch

Go only runs the selected case, not all the cases that follow. In effect, the break statement that is needed at the end of each case in those languages is provided automatically in Go.

Switch cases evaluate cases from top to bottom, stopping when a case succeeds.

switch i {
  case 0:
  case f():
}

does not call f if i == 0.

Another important difference is that Go’s switch cases need not be constants, and the values involved need not be integers.

Switch with no condition

Switch without a condition is the same as switch true. This construct can be a clean way to write long if-then-else chains.

switch {
case t.Hour() < 12:
	fmt.Println("Good morning!")
case t.Hour() < 17:
	fmt.Println("Good afternoon.")
default:
	fmt.Println("Good evening.")
}

Defer

A defer statement defers the execution of a function until the surrounding function returns. The deferred call’s arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.

func main() {
	defer fmt.Println("world")
	fmt.Println("hello")
}

Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.

Defer can be used for closing resources, e.g. filehandles.

See more Defer, Panic and Recover.

Pointers

Structs

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}

Struct fields are accessed using a dot.

Struct Pointers

To access the field X of a struct when we have the struct pointer p we could write (*p).X. However, that notation is cumbersome, so the language permits us instead to write just p.X, without the explicit dereference.

Struct Literals

A struct literal denotes a newly allocated struct value by listing the values of its fields. You can list just a subset of fields by using the Name: syntax. (And the order of named fields is irrelevant.)

var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

Arrays

The type [n]T is an array of n values of type T.

Declare a variable a as an array of ten integers.

var a [10]int
primes := [6]int{2, 3, 5, 7, 11, 13}

Slices

An array has a fixed size. A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array. In practice, slices are much more common than arrays.

The type []T is a slice with elements of type T.

A slice is formed by specifying two indices, a low and high bound, separated by a colon: a[low : high]. This selects a half-open range which includes the first element, but excludes the last one.

primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4] // -> 3, 5, 7

When slicing, you may omit the high or low bounds to use their defaults instead.The default is zero for the low bound and the length of the slice for the high bound.

Changing the elements of a slice modifies the corresponding elements of its underlying array. Other slices that share the same underlying array will see those changes.

Slice Literals

A slice literal is like an array literal without the length. This is an array literal:

[3]bool{true, true, false}

And this creates the same array as above, then builds a slice that references it:

[]bool{true, true, false}

Slice Length and Capacity

A slice has both a length and a capacity. The length of a slice is the number of elements it contains.

The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.

The length and capacity of a slice s can be obtained using the expressions len(s) and cap(s).

You can extend a slice’s length by re-slicing it, provided it has sufficient capacity.

s := []int{2, 3, 5, 7, 11, 13}
// Slice the slice to give it zero length.
s = s[:0] // len = 0, cap = 6
// Extend its length.
s = s[:4] // len = 4, cap = 6
// Drop its first two values.
s = s[2:] // len = 2, cap = 4

Creating a slice with make

Slices can be created with the built-in make function; this is how you create dynamically-sized arrays.

The make function allocates a zeroed array and returns a slice that refers to that array:

a := make([]int, 5)  // len = 5, cap = 5

To specify a capacity, pass a third argument to make:

b := make([]int, 0, 5) // len = 0, cap = 5

Appending to a slice

append appends to a slice, allocating a new if necessary.

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

Range - loop over a slice

The range form of the for loop iterates over a slice or map. Two values are returned for each iteration - the index, and a copy of the element.

for i, v := range slice {
  fmt.Printf("2**%d = %d\n", i, v)
}

The index or value can be skipped by assigning to _

for i, _ := range pow
for _, value := range pow

Map

A map maps keys to values.

m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
	40.68433, -74.39967,
}

Map literals:

var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

or in short with the type name omitted:

var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

Mutating maps

Function values

Functions are values too. They can be passed around just like other values.

Function closures

Go functions may be closures. A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables; in this sense the function is “bound” to the variables.

For example, the adder function returns a closure. Each closure is bound to its own sum variable.

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

Hardware

Go hardware

Cross compilation

set the relevant target os + arch: Optional environment variables

e.g. Windows 64 bit:

export GOOS=windows
export GOARCH=amd64
go install github.com/user/hello
# output goes to $GOPATH/bin/windows_amd64