Golang - ttulka/programming GitHub Wiki

Syntax

Packages

Every Go program is made up of packages.

  • Reusable: code used as "helpers".
  • Executable: generates a runnable file
    • Programs start running in package main.
    • Must have a func main()
package main

import (
    "fmt"
    "math/rand"
)

func main() {
    fmt.Println("My favorite number is", rand.Intn(10))
}

A name is exported if it begins with a capital letter.

Functions

A function can take zero or more arguments.

func add(x int, y int) int {
    return x + y
}
// or add(x, y int)

A function can return any number of results:

func swap(x, y string) (string, string) {
    return y, x
}
func main() {
    a,b := swap("hello", "world")
    fmt.Println(a, b)
} // prints world hello

Return values may be named:

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return
}
func main() {
    fmt.Println(split(17))
} // prints 7 10

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

  • may be used as function arguments and return values.
func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}
func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))       // 13
    fmt.Println(compute(hypot))     // 5
    fmt.Println(compute(math.Pow))  // 81
}

The closures functions may access and assign to the referenced variables:

func fibonacci() func() int {
    m := 0
    n := 1
    return func() int {
        tmp := m
        m = n
        n += tmp
        return tmp
    } }
func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    } }

Variables

The var statement declares a list of variables; as in function argument lists, the type is last.

  • can be at package or function level.
var c, python, java bool

func main() {
    var i int
    fmt.Println(i, c, python, java)
} // prints 0 false false false

A var declaration can include initializers, one per variable:

var i, j int = 1, 2

func main() {
    var c, python, java = true, false, "no!"
    fmt.Println(i, j, c, python, java)
}
  • If an initializer is present, the type can be omitted.

Inside a function, the := short assignment statement can be used in place of a var declaration with implicit type: func main() { i := 3 fmt.Println(i) } // prints 3

  • Outside a function the := construct is not available.

Redeclaration and reassignment

f, err := os.Open(name)
d, err := f.Stat()       // err is re-assigned 

Basic types

  • bool
  • string
  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64, uintptr
  • byte - alias for uint8
  • rune - alias for int32, represents a Unicode code point
  • float32, float64
  • complex64, complex128

The int, uint, and uintptr types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems.

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

i := 42
f := float64(i)
u := uint(f)
b := []byte("string")

When declaring a variable without specifying an explicit type the variable's type is inferred from the value on the right hand side:

var i int
j := i // j is an int

When the right hand side contains an untyped numeric constant, the new variable type depends on the precision of the constant:

i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

Constants expression is repeated by the other constants until another assignment or type declaration shows up.

  • cannot be declared using the := syntax.
  • const World = "Welt"

Iota are declared like variables, but with the const keyword.

type Day int

const (
    Monday Day = iota + 1
    Tuesday
    Wednesday
    ...
)

Zero values

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

  • 0 for numeric types,
  • false for the boolean type, and
  • "" (the empty string) for strings.
  • nil for pointers, slices, maps

For

Go has only one looping construct, the for loop.

  • init statement: executed before the first iteration
  • condition expression: evaluated before every iteration
  • post statement: executed at the end of every iteration
sum := 0
for i := 0; i < 10; i++ {
    sum += i
}
fmt.Println(sum)

The init and post statements are optional:

sum := 1
for ; sum < 1000; {  // for sum < 1000 {
    sum += sum
}
fmt.Println(sum)

If the loop condition is omitted it loops forever: for {}

If

if statement can start with a short statement to execute before the condition:

if v := math.Pow(x, n); v < lim {
    // do something
} else {
    // do else
}

Switch

Go only runs the selected case, not all the cases that follow.

  • break statement that is needed at the end of each case.
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)
}

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

switch i {
case 0:
case f():  // does not call `f` if `i==0`
}

Switch without a condition is the same as switch true.

  • can be a clean way to write long if-then-else chains.
t := time.Now()
switch {
case t.Hour() < 12:
    fmt.Println("Good morning!")
case t.Hour() < 17:
    fmt.Println("Good afternoon.")
default:
    fmt.Println("Good evening.")
}

Switch can have multiple case options:

switch protocol {
case "tcp", "tcp4", "tcp6", "unix":  // all is fine
default:
    fmt.Println("Unsupported protocol:", protocol)
    os.Exit(2)
}

Pointers

A pointer holds the memory address of a value.

  • The type *T is a pointer to a T value.
  • The & operator generates a pointer to its operand.
  • The * operator denotes the pointer's underlying value.
var p *int
i := 42
p = &i

fmt.Println(*p) // read i through the pointer p
*p = 21         // set i through the pointer p
type person struct {
    name string
}

func (p person) updateName1(name string) {
    p.name = name
}

func (p *person) updateName2(name string) {
    (*p).name = name
}

func (p *person) updateName3(name string) {
    p.name = name
}

func main() {
    jim := person{"Jim"}
  
    jim.updateName1("Jimmy1");  
    fmt.Println(jim)  // no change, passed by value
  
    (&jim).updateName2("Jimmy2A");  
    fmt.Println(jim)
  
    jim.updateName2("Jimmy2B");  
    fmt.Println(jim)
  
    (&jim).updateName3("Jimmy3A");  
    fmt.Println(jim)
  
    jim.updateName3("Jimmy3B");  
    fmt.Println(jim)
}
func updateString(s *string, v string) {
    *s = v
}
func main() {
    s := "abc"  
    updateString(&s, "def")
    fmt.Println(s)  // def
}

Value types need using pointers to change the value in a function:

  • int, float, string, bool
  • structs
  • arrays

Reference types don't need explicitly using pointers:

  • slices
  • maps
  • channels
  • functions
  • pointers

Everything in Go is pass by value:

func main() {
   s := "abc" 
   p := &s

   fmt.Println(p)   // 0xc000044240
   fmt.Println(&p)  // 0xc0000a0020
   printPointer(p)  // 0xc0000a0028
} 

func printPointer(p *string) {
   fmt.Println(&p)
}

Structs

A struct is a collection of fields.

  • Struct fields are accessed using a dot.
type Vertex struct {
    X int
    Y int
}
func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X)  // 4
}

Struct fields can be accessed through a struct pointer:

v := Vertex{1, 2}
p := &v
p.X = 1e5
fmt.Println(v)  // {100000 2}

A *struct literal denotes a newly allocated struct value by listing the values of its fields:

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

Embedding structs

type ReaderData struct {
    Read int
}
type WriterData struct {
    Written int
}

type ProcessorData struct {
    *ReaderData
    *WriterData
}

func main() {
    proc := ProcessorData{&ReaderData{123}, &WriterData{456}}	
    fmt.Println(proc.Read, proc.Written)  // 123 456
}

Arrays

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

  • var a [10]int declares a variable a as an array of ten integers.
  • arrays cannot be resized.
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])  // Hello World
fmt.Println(a)           // [Hello World]

primes := [5]int{2, 3, 5, 7, 11}
fmt.Println(primes)      // [2 3 5 7 11]

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

  • A slice is a dynamically-sized, flexible view into the elements of an array.
  • A slice is formed by specifying two indices, a low and high bound, separated by a colon: a[low : high]
  • includes the first element, but excludes the last one.
  • does not store any data, it just describes a section of an underlying array.
    • []int{1, 2, 3} creates an array, then builds a slice that references it.
primes := [6]int{2, 3, 5, 7, 11, 13}

var s []int = primes[1:4]
fmt.Println(s)      // [3 5 7]
	
s[0] = 999
fmt.Println(s)      // [999 5 7]

fmt.Println(primes) // [2 999 5 7 11 13]

High or low bounds can be omitted to use their defaults instead:

var a [10]int
// these are equivalent:
a[0:10]
a[:10]
a[0:]
a[:]

For a slice, Go will automatically create which two data structures:

  • an array,
  • a structure that records the length of the slice, the capacity of the slice, and a reference to the underlying array.

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.
s := []int{1, 2, 3, 4, 5, 6} // len(s)=6 cap(s)=6
s = s[2:4]                   // len(s)=2 cap(s)=4

Slices can be created with the built-in make function:

a := make([]int, 5)     // len(a)=5
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

Slices can contain any type, including other slices:

board := [][]string{
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
}
board[0][0] = "X"
board[2][2] = "O"

Slice values are not passed by value:

  • The slice is passed by value, but it still pointing to the original underlying array.
  • Slices are reference types.
func updateSlice(a []int, v int) {
    a[0] = v
}
func main() {
    a := []int{1, 2, 3}  
    updateSlice(a, 999)
    fmt.Println(a)  // [999 2 3]
}

Built-in append function appends new elements to a slice: 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.
s := []int{1, 2, 3}    // len=3 cap=3 [1 2 3]
s = append(s, 88, 99)  // len=5 cap=8 [1 2 3 88 99]

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

  • Two values are returned for each iteration: 1) index, 2) copy of the element at that index
pows := []int{2, 4, 8}

for i, v := range pows {
    fmt.Printf("%d=%d\n", i, v)
} // 0=2, 1=4, 2=8

You can skip the index or value by assigning to _:

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

for i := range pow  // only index

new vs make

Allocation with new

new, a built-in function allocates memory, but it does not initialize the memory, it only zeros it.

  • new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T.
Allocation with make

make(T, args), a built-in function creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T).

  • these three data structures must be initialized before use.
var p *[]int = new([]int)       // allocates slice structure; *p == nil; rarely useful
var v  []int = make([]int, 100) // the slice v now refers to a new array of 100 ints

// Unnecessarily complex:
var p *[]int = new([]int)
*p = make([]int, 100, 100)

// Idiomatic:
v := make([]int, 100)

Maps

A map maps keys to values.

m := make(map[string]int)
m["First"] = 1
m["Second"] = 2
// or
m := map[string]int{
    "First": 1,
    "Second": 2,
}

fmt.Println(m, m["First"], m["Second"])  
// map[First:1 Second:2] 1 2

delete(m, "Second")
second, ok := m["Second"]

fmt.Println(m, ok, second) 
// map[First:1] false 0

for k, v := range m {
    fmt.Println("Key", k, "has value", v)
}

Errors

Go programs express error state with error values.

  • The error type is a built-in interface:
type error interface {
    Error() string
}

Defer, Panic, and Recover

Defer statement pushes a function call onto a list.

  • A deferred function's arguments are evaluated when the defer statement is evaluated.
  • Deferred function calls are executed in Last In First Out order after the surrounding function returns.
  • Deferred functions may read and assign to the returning function's named return values.
// returns 2
func c() (i int) {
    defer func() { i++ }()
    return 1
}

Since defer will always get executed when the surrounding function returns, it is a good place to attach cleanup code such as:

  • Closing open files
  • Releasing network resources
  • Closing the Go channel
  • Committing database transactions
file, err := os.Open("data.txt")
if err != nil {
    return nil, err
}
defer file.Close()

Panic is a built-in function that stops the ordinary flow of control and begins panicking.

  • panic, a built-in function in effect creates a run-time error that will stop the program.
  • takes a single argument of arbitrary type—often a string—to be printed as the program dies.
func init() {
    if os.Getenv("USER") == "" {
        panic("no value for $USER")
    }
}

Recover is a built-in function that regains control of a panicking goroutine.

func main() {
    f()
    fmt.Println("Returned normally from f.")
}
func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f, cause:", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}
func g(i int) {
    if i > 3 {
        fmt.Printf("Panicking with %v!\n", i)
        panic(fmt.Sprintf("Wrong value (%v)", i))
    }	
    defer fmt.Println("Defer in g", i)	
    fmt.Println("Printing in g", i)	
    g(i + 1)
}

Methods

Method is just a function with a receiver argument defined on types.

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())
}
type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}
func main() {
    f := MyFloat(-math.Sqrt2)
    fmt.Println(f.Abs())
}

Methods which need to modify their receiver are using pointer receivers:

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}
  • More efficient as it doesn't copy the value on each method call.

Interfaces

An interface type is defined as a set of method signatures.

  • Calling a method on an interface value executes the method of the same name on its underlying type.
  • If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
    • it is common to write methods that gracefully handle being called with a nil receiver (if t == nil)
type Abser interface {
    Abs() float64
}
type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}
func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)

    a = f  // a MyFloat implements Abser

    fmt.Println(a.Abs())  // 1.4142135623730951
}

Assembling interface brings multiple interfaces together to create another interface.

type ReadCloser interface {
	Reader
	Closer
}
type Reader interface {
	Read(p []byte) (n int, err error)
}
type Closer interface {
	Close() error
}

The interface type that specifies zero methods is known as the empty interface: interface{}.

  • An empty interface may hold values of any type.
  • Empty interfaces are used by code that handles values of unknown type.
func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i)
}
func main() {
    var i interface{}
    describe(i)

    i = 42
    describe(i)

    i = "hello"
    describe(i)
}

A type assertion provides access to an interface value's underlying concrete value.

  • Asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t.
  • If i does not hold a T, the statement will trigger a panic.
  • t := i.(T) or t, ok := i.(T)
var i interface{} = "hello"

s := i.(string)
fmt.Println(s)      // hello

s, ok := i.(string)
fmt.Println(s, ok)  // hello true

f, ok := i.(float64)
fmt.Println(f, ok)  // 0 false

f = i.(float64) // panic

Stringer

Stringer is one of the most ubiquitous interfaces 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.
type Person struct {
    Name string
    Age  int
}
func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

Embedding

Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.

type Reader interface {
    Read(p []byte) (n int, err error)
}
type Writer interface {
    Write(p []byte) (n int, err error)
}
// Processor can do what Reader does and what Writer does
type Processor interface {
    Reader
    Writer
}

Goroutines

A goroutine is a lightweight thread managed by the Go runtime.

  • go f(x, y, z) starts a new goroutine running f(x, y, z).
  • Goroutines run in the same address space, so access to shared memory must be synchronized.
func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}
func main() {
    go say("world")
    say("hello")
} // prints world hello hello world world hello ...

We can use function literals in goroutines:

go func() {
    time.Sleep(5 * time.Second)
    doSomethingConcurrently()
}()

A variable should be passed as a parameter, rather than reading it from a closure:

starts := []int{10,40,70,100}
for _, j := range starts{
    go func(s int) {
        count(s, s + 20, 10)
    }(j)
}

Select

The select statement lets a goroutine wait on multiple communication operations.

  • A select blocks until one of its cases can run, then it executes that case.
func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
    select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }    }
}
func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
} // prints 0 1 1 2 3 5 8 13 21 34 quit

The default case in is run if no other case is ready.

select {
case i := <-c:
    // use i
default:
    fmt.Println("Channel not ready yet...")
    time.Sleep(50 * time.Millisecond)
}

Channels

Channels are a typed conduit through which you can send and receive values with the channel operator <-.

ch <- v    // Send v to channel ch.
v := <-ch  // Receive from ch, and assign value to v.
  • The data flows in the direction of the arrow.
  • Channels must be created before use: ch := make(chan int)
  • By default, sends and receives block until the other side is ready.
  • Can be declared as send- or receive-only: func doSmt(in chan<- int, out <-chan int) { }`
func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c <- sum // send sum to c
}
func main() {
    s := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := <-c, <-c // receive from c

    fmt.Println(x, y, x+y)  // -5 17 12
}

Channels can be buffered. Provide the buffer length: ch := make(chan int, 128)

  • Sends to a buffered channel block only when the buffer is full.
  • Receives block when the buffer is empty.

Sending to an unbuffered channel can easily cause a deadlock if the operation is not wrapped in a goroutine:

ch := make(chan int)
ch <- 12             // deadlock
fmt.Println(<-ch)
  • Fix by using a buffered channel: ch := make(chan int, 2)
  • Fix by using a goroutine: go func() { ch <- 1 }()

Sender can close the channel to indicate that no more values will be sent.

  • Receivers can test whether a channel has been closed by assigning a second parameter v, ok := <-ch
  • The loop for i := range c receives values from the channel repeatedly until it is closed.
func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)
}
func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
} // prints 0 1 1 2 3 5 8 13 21 34

sync.Mutex

Go's standard library provides mutual exclusion with sync.Mutex and its methods: Lock and Unlock.

var mux sync.Mutex
mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
value++
mux.Unlock()

Printing

// each line produces the same output
fmt.Printf("Hello %d\n", 23)
fmt.Fprint(os.Stdout, "Hello ", 23, "\n")
fmt.Println("Hello", 23)
fmt.Println(fmt.Sprint("Hello ", 23))
fmt.Println(name, `says "Hello!"`)

Wait for a value to be sent into a channel, then print it immediately:

fmt.Println(<- channel)

Organization

  • Keep all Go code in a single workspace.
  • A workspace contains many version control repositories.
  • Each repository contains one or more packages.
  • Each package consists of one or more Go source files in a single directory.
  • The path to a package's directory determines its import path.

Workspaces

A workspace is a directory hierarchy with two directories at its root:

  • src contains Go source files, and
  • bin contains executable commands.
bin
  └─hello
src
  └─github.com
    └─ttulka
      └─golang-samples
          ├─hello
          │ └─hello.go
          └─stringutil
            └─stringutil.go

GOPATH

The GOPATH environment variable specifies the location of your workspace.

  • go env GOPATH

GOBIN

The GOBIN environment variable specifies the location where binaries are installed.

export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN

GOROOT

The GOROOT environment variable specifies the Go SDK location.

export GOROOT=$HOME/go
export PATH=$PATH:$GOROOT/bin

GOOS

The GOOS environment variable specifies the target operation system for which to compile code.

  • linux, darwin, windows, ...
GOOS=windows GOARCH=amd64 go install

GOARCH

The GOARCH environment variable specifies the architecture, or processor, for which to compile code.

  • amd64, 386, arm, ppc64, ...

Install a program

go install $GOPATH/src/github.com/ttulka/golang-samples/hello
$GOPATH/bin/hello

Build a library

go build $GOPATH/src/github.com/ttulka/golang-samples/stringutil
import (
    "fmt"
    "github.com/ttulka/golang-samples/stringutil"
) 

Run a program from source code

go run $GOPATH/src/github.com/ttulka/golang-samples/hello/hello.go

Download a remote source code

go get -u github.com/gorilla/mux

Testing

Go has a lightweight test framework composed of the go test command and the testing package.

go test $GOPATH/src/github.com/ttulka/golang-samples/stringutil
go test -cover  // with coverage
import "testing"

func TestReverse(t *testing.T) {
    got := reverse("abc")

    if got != "cba" {
        t.Errorf("Expected 'cba', but got '%v'", got)
    }
}	

Formatting

// a file or all files direct in the folder
go fmt $GOPATH/src/github.com/ttulka/golang-samples/hello/hello.go
go fmt $GOPATH/src/github.com/ttulka/golang-samples/hello
// all files recursively 
gofmt $GOPATH/src/github.com/ttulka/golang-samples

References