Learning Go – Day 1

Featured image by Egon Elbre

I decided that it was finally time to sit down and learn me some Golang.

Please note that this post and the following posts in my “series” of Learning Go is meant to be notes to my future self and not meant to be a tutorial series for anyone else reading this. Therefore there will be lots of prebaked-in-assumptions and context that any other reader might not have. Again these are notes to myself.

Before Day 1 there was Day 0

Day 1

Ok so at this point I decided Go looks amazing and that it is worth my time learning it.

$ mkdir -p ~/Learn/Go/golangbot-series
$ cd ~/Learn/Go/golangbot-series

Very basics

  • Create a new module using go mod init. This will create a go.mod file in the current directory.
  • Use go run . to run the current module. It looks for a .go file that is part of the main package and runs the main function. NOTE: It does not leave build artifacts in the current directory.
  • Can use --work with go run to see which temporary directory is used to build and run from.
$ go run --work .
WORK=/var/folders/qv/nt2bk76d50d2j53w12thd0200000gn/T/go-build2629236756
Hello world!
  • Use go build to build an executable. By default it is placed in the current directory.
  • Can cross compile by setting the GOOS environment variables.
$ GOOS=linux go build
$ GOOS=windows go build

Variable declaration

// var name type
var age int
var age int = 5
var age = 5
// short hand declaration
age := 5

// declare one or more of same type
// note: this will be initialized to 0
var x, y int

x, y, name := -2, 42, "Jannie"
  • Can declare multiple variables in this manner
var (
		name = "Andre"
		age = 5
	)
  • Basic types
bool // true, false
byte // alias for uint8
int, int8, int16, int32, int64
uint, uint8, uint16, uint32, uint64
float32, float64
complex64, complex128
rune // alias for int32 and is used as Unicode code point
string
  • Can find out the type and size
import "unsafe"
fmt.Printf("type of a is %T, size of a is %d\n", a, unsafe.Sizeof(a))
  • No implicit type casting, meaning you have to explicitly cast a type to be used with another type. int(floatVar), float64(intVar)
  • Constants
const a = 5
const (
	age = 5
	name = "Jannie"
)
  • Constants can be “untyped”. See this playground for “a”.

Functions

func name(param1 type, param2 type) returnType {  
...
}

func name(param1, param2 sameType) ...
  • Returning more than one type
func something(timesTwo, timesThree int) (int, int) {
	return timesTwo * 2, timesThree * 3
}

multTwo, multThree := something(2, 2)
fmt.Println(multTwo, multThree)
  • Named return types. The named types are treated like variables inside the body.
func rectProps(length, width float64) (area, perimeter float64) {  
    area = length * width
    perimeter = (length + width) * 2
    return //no explicit return value
}
  • Like Swift you can ignore results you are not interested in with _
_, mult3 := somefunction()

Packages

  • Packages are a collection of Go sources files that reside in the same directory.
  • Go Module is a collection of Packages.
  • Every Go executable must have a main package and main function.
  • Create a new module
$ go mod init nameOfModule
  • Why use PascalCasing?

Any variable or function which starts with a capital letter are exported names in go. Only exported functions and variables can be accessed from other packages.

  • Packages can have initializer functions. For more details on how packages are initialized, see this link.
    • Package level variables are initialised first.
    • Imported packages are initialized.
    • Package’s init functions are called in the order they are presented to the compiler.
    • Package is only initialized once.
  • Compiler will complain if you import a package and not use it.
  • To ensure a package was initialized even though we don’t use it.
import (
	_ "somemodule/package"
)

Control flow

  • If
if condition1 {  
...
} else if condition2 {
...
} else {
...
}
  • IMPORTANT NOTE: The else must be on the same line as the } because of the way Go automatically inserts ; (See this link)
  • If with assignment
if assignment; condition {  
}

// e.g.
if number := 42; number < 100 { ...

Loops

  • There is only the for loop in Go. It looks like C.
for initialisation; condition; post {  
}
  • Infinite loop
for {
...
}
  • break to break out of the loop.
  • continue to start the next iteration of the loop.
  • Can use labels
mylabel:
	for ... {
		for ... {
		break mylabel
...

Switch

switch something {
case 1:
	...
case 2, 3, 4:
  ...
	if something {
		break
  }
  ...
case 400:
	fallthrough // no automatic fall throughs
case 1000:
	...
default:
	...
}
  • Expression less switch
switch { // <-- note there is no expression to evaluate here
case number < 5:
    ...
case number > 5 && number < 10:
    ...
case somethingElse:
    ...
}

Arrays (fixed sized)

  • Type declaration: [n]T, where T is the type and n is the number of elements.
var names [5]string
var scores [10]int // initialized all 0

// short hand declaration
a := [3]int{12, 78, 50}
a := [3]int{12} // only index:0 = 12, the rest = 0

a := [...]int{12, 78, 50} // let the compiler figure out the number of elements
  • Arrays can not be resized (i.e. allocated with a fixed size).
  • Arrays are value types like in Swift. Meaning a copy is made when passed around or assigned to different variables.
  • Use len() to find the number of elements the array can hold.
a := [1, 2, 3, 4, 42]
len(a) // 5
  • You could loop over an array using for i := 0; i < len(a); i++ however range is better and more idiomatic.
for i, v := range a {
 // i is the index and v is the element value
}
  • Go supports multi-dimensional arrays.
var matrix := [3][3]float64

Slices (dynamic size)

  • Used to reference existing arrays (i.e. take a slice)
  • Type declaration: []T (contrast this with [n]T for an array)
a := [5]int{76, 77, 78, 79, 80}
var b []int = a[1:3] // take a slice from a[1] to a[2]
// ^^ notice how :3 means up to 3 but not including

// Create an array with 3 elements and then return a slice to reference it
c := []int{1, 2, 3}

d := a[:] // slice that references the entire array
  • Changes made to a slice, will be reflected in the referenced array the slice was taken from.
  • Use len() to find the number of elements the slice references from the existing array.
  • Slices have a “capacity” and you can use cap() to find out what it is. Slices can be resliced up to its capacity.
a := [...]int{10, 20, 30, 40, 50}
s := a[1:3] // [20, 30]
fmt.Println(len(s), cap(s)) // 2 4

// reslice s to reference all of its capacity
s = s[:cap(s)] // [20, 30, 40, 50]
fmt.Println(len(s), cap(s)) // 4 4
  • Creating a slice using make.
// func make([]T, len, cap) []T
// capacity is optional and if omitted the capacity will be the same as length
s := make([]int, 5, 42) // [0 0 0 0 0]
  • Append new elements using append function. This will allocate a new array (double the size) if the capacity of the slice is exceeded. Remember: append returns a new slice.
s := make([]int, 4, 6)
fmt.Println(len(s), cap(s)) // 4 6

for i := 0; i < 3; i++ {
	s[i] = i
}
fmt.Println(len(s), cap(s)) // 4 6

s = append(s, 100)
s = append(s, 200)
fmt.Println(len(s), cap(s)) // 6 6
s = append(s, 400) // bigger array is needed
fmt.Println(len(s), cap(s)) // 7 12 <-- a new array of size 12 has been allocated
  • Append two slices together.
a := [...]int{1, 2, 3}
b := [...]int{4, 5, 6}
// note: we are passing a slice a[:] and slice b[:] to append
c := append(a[:], b[:]...)
fmt.Println(c) // [1 2 3 4 5 6]
  • Slices are passed (to functions) as value types, HOWEVER they reference the real array and thus changes made to slices inside of a function will be reflected in the referenced array.
  • Slices can also be multi-dimensional.
  • Remember Go is garbage collected and thus a slice references an existing array and the array will not be collected (freed) until all references are gone as well. Use copy() function to copy slices (i.e. copy to a smaller slice from a bigger referenced array)

Variadic functions

  • A function that can take a variable number of arguments at runtime.
func something(a int, b bool, c ...float64) {
...
}

something(1, true)
something(1, true, 2.0)
something(1, true, 2.0, 3.1, 4.2, 5.3)
  • The variadic argument is converted to a slice.
  • Passing a slice as the variadic argument.
s := [...]int{2.0, 3.0}
something(0, true, s) // won't work! because it expects float64 and we are passing a slice
// however
something(0, true, s...) // will work. thank you compiler :-D