1. Keyword Declarations
  2. Functions
  3. Var Declarations
  4. Types
    1. Type Casting
  5. Flow & Control
    1. Looping
      1. For Loops
      2. “While” Loops
      3. Infinite Loops
    2. If Statements
      1. Switch Statements
      2. Condition-less Switch Statements
    3. Defer Keyword
  6. Structs
    1. Types
  7. Pointers
  8. Arrays
    1. Slices
    2. Slice Literals
    3. Make()
    4. Nested Slices
    5. Append
    6. Range
  9. Maps
    1. Map Literals
    2. Map Functions
    3. Functions as Parameters
    4. Function Closures
  10. Methods
    1. Pointer Receivers
  11. Interfaces
    1. Empty Interface
    2. Testing an Interface Type
      1. Type Switches
    3. Aside: Stringers
  12. Errors
  13. IO Package
    1. Reader

Keyword Declarations

  • func
  • var
  • const → Cannot be declared with :=

Functions

  • Simple Declaration: func name(arg1 int, arg2 int) int {}
  • Named Return Values: func name(arg1 int) (a, b int) {}
    • Only return needs to be called, the values given to a and b will be returned

Var Declarations

  • Anywhere: using var a int or multiple types → var a, b, c int
    • With initialiser → var i int = 1 or var a, b, c = 1, 2, "hi"
    • Declaring without initialiser chooses default value (0, false, "")
  • Inside func → can use a short assignment declaration
    • x, y := 1, 2

Types

Main Basic Types:

  • bool
  • string
  • int → Defaults
  • byte
  • float32 and float64
  • complex64

Type Casting

  • Express T(v) converts v to type T
    • e.g. var i int = 42 var f float32 = float32(i)
    • or i := 42 f := uint32(i)

Flow & Control

Looping

For Loops

for i := 0; i < 10; i++ {
	fmt.Println(i)
}

“While” Loops

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

Infinite Loops

for {
	fmt.Println("Looping endlessly")
}

If Statements

if mark > 0.7 {
	fmt.Println("Great")
}
// Concise statement preceeding
if a := math.Pow(x, n); a < 100 {
	fmt.Println(a)
} else {
	fmt.Printf("a is still in scope: %g\n", v)
}
// a is no longer in scope here

Switch Statements

switch /*preliminary statement;*/ sw {
	case "a":
		// do something
	case f():
	default:
		// only executes when case "a" not true
		// do something else
}

Condition-less Switch Statements

Used as a clean way to write long if-then-else chains

switch {
	case a < b:
		// Do something
	case a < c:
		// Do something else
	default:
		// Default case
}

Defer Keyword

  • Preceding a function call with defer will cause it to be called only after the function returns
  • When multiple are used in the same function, they will be stacked in a LIFO order

Structs

type Vertex struct {
	X int // Need not be capitalised
	Y int
}
func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X) // prints 4
}
s := []struct {
		i int
		b bool
	}{
		{2, true},
		{3, false}
	}

Types

  • To rename an existing struct/type, the type keyword can be used:
    • type MyFloat float64
  • These can then be used as a method type

Pointers

  • In essence, very similar to C pointers → But, there is no pointer arithmetic

      i, j := 42, 2701
      v := Vertex{1, 2}
    
      p := &i         // point to i
      fmt.Println(*p) // read i through the pointer
      *p = 21         // set i through the pointer
      fmt.Println(i)  // see the new value of i
    
      p = &j         // point to j
      *p = *p / 37   // divide j through the pointer
      fmt.Println(j) // see the new value of j
    
      // Dereference structs as follows
      pV := &v
      fmt.Println(pV.X == p.X) // prints true -> (*pV).X === pV.X 
    
    

Arrays

  • Declared with var a [n]T → e.g. var a [10]int
    • Through short assignment declaration → a := [3]string{"a", "b", "c"}
  • Arrays cannot be resized

Slices

  • Dynamically sized, flexible view into the elements of an array
  • Type []T is a slice with elements of type T
    • e.g. b []int = a[1:5] or b := a[0:4]
  • Formed by specifying two indices, low and high bound, separated by a colon [low, high)
    • a[low : high]
    • e.g. a[1:4] contains elems 1, 2 & 3
  • They do not store anything → more like a description of a section of an underlying array
  • Changing elements of a slice modify the elements in the array and changes will propagate to other slices of the same array
  • len(s) gives length of the slice, cap(s) gives length of the underlying array
  • Zero value: nil → capacity & length of 0 → no underlying array

Slice Literals

  • Can be used to produce an array from some values and return a slice of that array
  • [3]bool{true, true, false}[]bool{true, true, false}

Make()

  • By using make, you can allocate a zeroed array while returning a slice referring to that array → make(<slice type>, <slice length>, [optional]<array length>)
  • a := make([]int, 5) gives len(a) = 5 and cap(a) = 5
  • b := make([]int, 5, 10) gives len(b) = 5 and cap(b) = 10

Nested Slices

board := [][]string{
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
	}

Append

  • Use func append(source []T, v1 T, v2 T) []T to append values to a slice.
    • e.g. s = append(s, 1)
  • Note: If the array is too small, a new one will be allocated

Range

  • Iterating over a slice or map
    • Works like enumerate, returns index, value
      var pow = []int{1, 2, 4, 8}
      for i, v := range pow {
      	fmt.Printf("2^%d = %d\n", i, v)
      }
    
    • Can also use for i := range pow, for _, v := range pow

Maps

  • Defined using var m map[string]string or var m map[keyT]valT
  • Initialised using m = make(map[string]string)

Map Literals

  • Maps can be initialised using map literals, similar to structs

      var m = map[string]int{
      	"Bell Labs": 5,
      	"Google": 3
      }
    
  • If the map value type is a struct, it can be omitted from the elements in the literal

      type Vertex struct {
      	Lat, Long float64
      }
    
      m := map[string]Vertex{
      	"Bell Labs": {40.68433, -74.39967},
      	"Google":    {37.42202, -122.08408},
      }
    

Map Functions

  • Insert → m[key] = elem
  • Read → elem = m[key]
  • Delete → delete(m, key)
  • Test presence elem, ok := m[key] (value of ok is true if key is present in m)

Functions as Parameters

  • Accepting function’s parameters → func fname(fn func(T1, T2, ...) TRet) T
    • e.g.

        func compute(fn func(float64, float64) float64) float64 {
        	return fn(3, 4)
        }
        hypot := func(x, y float64) float64 {
        	return math.Sqrt(x*x + y*y)
        }
        fmt.Println(hypot(5, 12))
      

Function Closures

By returning a function, variables outside the function can be accessed

e.g.

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),
		)
	}
}

Methods

  • To replace classes, Go has methods you can define on types and structs
  • Methods can be defined with a receiver argument
  • e.g.

      type Vertex struct {
      	X, Y float64
      }
      func (v Vertex) Magnitude() float64 {
      	return math.Sqrt(v.X*v.X + v.Y*v.Y)
      }
      // main
      v := Vertex{3, 4}
      fmt.Println(v.Magnitude())
    
  • e.g. with type

      type MyFloat float64
      func (f MyFloat) isPos() bool {
      	return f > 0
      }
      // main
      fmt.Println(MyFloat(-1).isPos())
    

Pointer Receivers

  • More commonly, the value in the type will need to be mutated
    • Or, the struct shouldn’t be copied for performance reasons
  • Use a pointer receiver in method constructor
    • e.g. func (v Vertex) Magnitude(f float64)func (v *Vertex) Transform(f float64)
    • and v.Magnitude()v.Transform(5)
  • NB: All methods can be called from both values and pointers to those values

For most needs, Pointer Receivers > Argument Receivers

And different kinds of methods should not be used within the same type

Interfaces

  • A type defined as a set of method signatures
  • Think of this as a slice but for types instead of arrays
  • Allows many methods with the same name to be bound to different types
  • e.g.
type I interface {
	M()
}
type T struct {
	S string
}
func (t T) M() {
	fmt.Println(t.S)
}
type F float64
func (f F) M() {
	fmt.Println(f)
}
func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
func main() {
	var i I
	i = &T{"Hello"}
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
}

Empty Interface

  • Empty interfaces, defined with var i interface{}, can hold values of any type
  • Useful for code that handles unknown type values, such as fmt.Print()

Testing an Interface Type

  • Use t, ok := i.(T) where i is the interface instance and T is the test type
    • t will be underlying value and ok will be true if i holds a T
    • Otherwise, t will be a zero value of T and ok will be false
    • (Similar syntax to reading from a map)

Type Switches

  • Use a switch statement with keyword type to test for multiple interface types
switch v:= i.(type) {
	case int:
		// ...
	case string:
		// ...
	default:
		// ...
}

Aside: Stringers

  • Used to pretty-print a value when fmt.Print is used
  • A Stringer is a type that can describe itself as a string. By using a func (t T) String() string {return ...}
    • e.g.
      func (p Person) String() string {
      	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
      }
    

Errors

  • Built-in interface similar to Stringer
  • Set up as follows

      type MyError string
      func (e *MyError) Error() string {
      	return fmt.Sprintf("Problemo: %s is invalid", e)
      }
      func randomFunc(x float64) float64, error {
      	float64 y
      	if x < 0 {
      		return y, &MyError{x}
      	}
      	return 1 / x
      }
    
  • type MyError struct ..., error function func (e *MyError) Error() string {} and calling function signature func (...) ..., error {}
  • Test for error with if err != nil {}

IO Package

Reader

  • Use func (T) Read(b []byte) (n int, err error) with a byte slice b := make([]byte, 8)
  • With n, err := r.Read(b) where r := strings.NewReader("temp"), n contains the number of bytes populated and err contains nil or io.EOF on stream end