GoLang Notes
This is a (non-exhaustive) compilation of my Go Notes from A Tour of Go.
- Keyword Declarations
- Functions
- Var Declarations
- Types
- Flow & Control
- Structs
- Pointers
- Arrays
- Maps
- Methods
- Interfaces
- Errors
IOPackage
Keyword Declarations
funcvarconst→ 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
returnneeds to be called, the values given toaandbwill be returned
- Only
Var Declarations
- Anywhere: using
var a intor multiple types →var a, b, c int- With initialiser →
var i int = 1orvar a, b, c = 1, 2, "hi" - Declaring without initialiser chooses default value (
0,false,"")
- With initialiser →
- Inside
func→ can use a short assignment declarationx, y := 1, 2
Types
Main Basic Types:
boolstringint→ Defaultsbytefloat32andfloat64complex64
Type Casting
- Express
T(v)convertsvto typeT- e.g.
var i int = 42 var f float32 = float32(i) - or
i := 42 f := uint32(i)
- e.g.
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
deferwill 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
typekeyword 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"}
- Through short assignment declaration →
- Arrays cannot be resized
Slices
- Dynamically sized, flexible view into the elements of an array
- Type
[]Tis a slice with elements of typeT- e.g.
b []int = a[1:5]orb := a[0:4]
- e.g.
- 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)giveslen(a) = 5andcap(a) = 5b := make([]int, 5, 10)giveslen(b) = 5andcap(b) = 10
Nested Slices
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
Append
- Use
func append(source []T, v1 T, v2 T) []Tto append values to a slice.- e.g.
s = append(s, 1)
- e.g.
- 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
- Works like enumerate, returns
Maps
- Defined using
var m map[string]stringorvar m map[keyT]valT - Initialised using
m = make(map[string]string)
Map Literals
-
Maps can be initialised using map literals, similar to
structsvar 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 ofokistrueifkeyis present inm)
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)
- e.g.
- 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)whereiis the interface instance andTis the test typetwill be underlying value andokwill be true ifiholds aT- Otherwise,
twill be a zero value ofTandokwill be false - (Similar syntax to reading from a map)
Type Switches
- Use a
switchstatement with keywordtypeto test for multiple interface types
switch v:= i.(type) {
case int:
// ...
case string:
// ...
default:
// ...
}
Aside: Stringers
- Used to pretty-print a value when
fmt.Printis used - A
Stringeris a type that can describe itself as a string. By using afunc (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 functionfunc (e *MyError) Error() string {}and calling function signaturefunc (...) ..., error {}- Test for error with
if err != nil {}
IO Package
Reader
- Use
func (T) Read(b []byte) (n int, err error)with a byte sliceb := make([]byte, 8) - With
n, err := r.Read(b)wherer := strings.NewReader("temp"),ncontains the number of bytes populated anderrcontainsnilorio.EOFon stream end