Skip to content

Latest commit

 

History

History

go

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

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

Package builtin documents the builtin functions and identifiers

two formats:

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

a name is exported if it begins with a capital letter

a function can return any number of results:

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

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.
}

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

var isOk, isError bool

if an initializer is present the type can be omitted:

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

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

c := true
  • 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

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

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

The zero value is:

  • 0 for numeric types,
  • false for the boolean type,
  • "" (the empty string) for strings.

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)

Print the type of a variable:

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

Declared with the const keyword.

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

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

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

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

Infinite loop.

for {
  // do something
}

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
}

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 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.")
}

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.

  • The type *T is a pointer to a T value. Its zero value is nil.
    var p *int
  • The & operator generates a pointer to its operand.
i := 42
p = &i
  • The * operator denotes the pointer's underlying value ("dereferencing" or "indirecting").
fmt.Println(*p) // read i through the pointer p
*p = 21         // set i through the pointer p
type Vertex struct {
	X int
	Y int
}

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

Struct fields are accessed using a dot.

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.

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
)

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}

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.

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}

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

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

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

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

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

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},
}
  • insert/update: m[key] = elem
  • get: elem = m[key]
  • delete: delete(m, key)
  • test for presence: 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.

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

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