Go Lang

Notes are of https://tour.golang.org/

Variables

Declared with var

The := operator

package main
import "fmt"

func main() {
        fmt.Println("counting")

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

    fmt.Println("done")
}

Pointers

This is mostly the same as C with one exception, there is no pointer arithmetic!

The & operator generates a pointer to its operand.

The * operator denotes the pointer’s underlying value.

Structs

Almost what you think, you just need to prepend type <struct type> struct {}

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    fmt.Println(Vertex{1, 2})
    v.X = 4
        fmt.Println(v.X)
}

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.

p = &v
p.X = 1e9

When initializing You can specify a subset of values and the rest will implicitly be set to their default values

v2 = Vertex{X: 1}
// v2.X = 1, v2.Y = 0

Arrays

var a [10]int

Slices

They are syntactic surgar for references to a portion of the underlying array thus do not create copies of the data. Super fast and efficient.

a[low : high]

The zero value of a slice is nil.

A nil slice has a length and capacity of 0 and has no underlying array.

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

Make

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

a := make([]int, 5)  // len(a)=5
// To specify a capacity, pass a third argument to make:

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

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4

Append

It is common to append new elements to a slice, and so Go provides a built-in append function. The documentation of the built-in package describes append.

func append(s []T, vs ...T) []T

The first parameter s of append is a slice of type T, and the rest are T values to append to the slice.

The resulting value of append is a slice containing all the elements of the original slice plus the provided values.

If the backing array of s is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.

Range

The range form of the for loop iterates over a slice or map.

When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

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

Maps

Is there an element in the map?

elem, ok = m[key]

If key is in m, ok is true. If not, ok is false.

If key is not in the map, then elem is the zero value for the map’s element type.

Collections

  • Arrays

  • Slices

  • Maps

Functions

They are first class citizens and can be treated as values.

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.

Conditionals

Loops and if statements live here!

Loops

There are no “while” loops in go, and () are not needed in the conditional controlling the loop but {} are need to delimit the end of the loop unlike some languages (Gives a dirty look at Python [jk I love Python :D])

//TODO

Switch Statements

Switch statements work how you think they do

func main() {
    fmt.Print("Go runs on ")
        switch os := runtime.GOOS; os {
        case "darwin":
                fmt.Println("OS X.")
        case "linux":
                fmt.Println("Linux.")
        default:
                // freebsd, openbsd,
                // plan9, windows...
                fmt.Printf("%s.\n", os)
        }
}

Well actually they’re better than you think they are. Switch statements can switch on types.A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.

switch v := i.(type) {
case T:
    // here v has type T
case S:
    // here v has type S
default:
    // no match; here v has the same type as i
}

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.

package main

import "fmt"

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

        fmt.Println("hello")
}

The above code outputs:

hello\n world

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

To learn more about defer statements read this blog post.

Methods

Go has no classes, but you can define methods for structs. It’s got a wack syntax though using recievers

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}

You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).

Pointer Receivers

By default GO is pass by value, so to have a method change the struct it was called on it has to be defined with a pointer receiver.

func (v *Vertex) Scale {}

The above can change struct members

Interfaces

An interface type is defined as a set of method signatures. A value of interface type can hold any value that implements those methods.

Interfaces are implemented implicitly. A type implements an interface by implementing its methods. There is no explicit declaration of intent, no “implements” keyword.

Less complex Ex

package main

import "fmt"

type I interface {
    M()
}

type T struct {
    S string
}

// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
    fmt.Println(t.S)
}

func main() {
    var i I = T{"hello"}
    i.M()
}

More complex

package main

import (
    "fmt"
    "math"
)

type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f  // a MyFloat implements Abser
    a = &v // a *Vertex implements Abser

    // In the following line, v is a Vertex (not *Vertex)
    // and does NOT implement Abser.
    a = v

    fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

Stuff on Type assertion and empty interfaces. How to avoid a panic when using type assertions,

Stringers

One of the most ubiquitous interfaces is Stringer defined by the fmt package.

type Stringer interface {
    String() string
}

A Stringer is a type that can describe itself as a string. The fmt package (and many others) look for this interface to print values.

Readers

The io package specifies the io.Reader interface, which represents the read end of a stream of data.

The io.Reader interface has a Read method:

func (T) Read(b []byte) (n int, err error)

Read populates the given byte slice with data and returns the number of bytes populated and an error value. It returns an io.EOF error when the stream ends.

Error Propegation

Errors are handled through the error interface.

type error interface {
    Error() string
}

Modules

Automated testing

Goroutines