Learning the basics of Go.

Besides a simple hello world, I've never code anything in Go prior. So I'll start at the beginning.

The first thing I'll do is to checkout syntax and how Go works. By the way I'll be writing this mainly for myself, so it will be in the same structure I write my personal notes. Not exactly an article.

Summary:

  1. Every Go program is made up of packages and it starts running in package main. By convention, the package name is the same as the last element of the import path. So "math/rand" package comprises files that begin with the statement package rand
  2. You can import packages in two ways:
// Factored import statements; import ( "fmt" "math" ) // multiple import statements (recommended); import "fmt" import "math"
  1. A name is exported if it begins with a capital letter. A variable named Pi is exported but pi isn't.
  2. Functions can take 0..n arguments and the types of those arguments come after the variable name:
func add(x int, y int) int { return x + y } // if one or more arguments have the same type you can just omit all but the last: func subtr(x, y int) int { return x - y }
  1. A function can also return multiple values:
package main import "fmt" // f(str str) (str str) func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("hello", "world") fmt.Println(a, b) } // output: "world hello" // You can also omit the named values of the return like so: func split(num int) (x, y int) { x = num / 10 y = num % 10 return } // split(75) outputs: 7 5
  1. You may declare variables with the var keyword. Both in package and function level.
package main var i, j int // i = 0, j = 0 var yes, no bool = true, false // yes = true, no = false func main() { // Inside functions varibles may be declared using a short notation: hundred := 100 }
  1. 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 // int, uint, uintptr are 32 bits on 32bit systems and 64 bits on 64 bits systems.
  1. Variables declared without explicit initial values are given their zero value: 0 for numeric types, false for booleans and "" for strings.
  2. You can convert types using T(v) where the value v is converted to type T:
var i int = 42 var f float64 = float64(i) var u uint = uint(f)
  1. Variables declared without their types declared have inferred types based on their initial value:
i := 42 // int f := 3.142 // float64 g := 0.867 + 0.5i // complex128
  1. constants are declared using the const keyword, and can be either string, boolean or numeric values. Untyped constants takes the type needed by it's context.
  2. The only looping construct in Go is the for loop.
package main import "fmt" func main() { sum := 0 // init; condition; post // expressions like for and ifs don't need to have // parentheses but they require braces. for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) //the init and post statements are optional // making the for work as while for sum < 1000 { sum += sum } fmt.Println(sum) // and this makes an infinite loop for { } // if statements accept declarations before // the condition if v := sum ** 2; v % 42 == 0 { fmt.PrintLn('this is it') } else { fmt.PrintLn('not it, but i have access to v') sum += v } }
  1. Switch statements:
package main import ( "fmt" "runtime" ) 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) } }
  1. a defer statement defers the execution of a function until the surrounding function returns. The deferred calls arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.
  2. Deferred values are put onto a stack, and the values are evaluated in LIFO order:
package main import "fmt" func main() { fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done") } /* output: counting done 9 8 7 6 5 4 3 2 1 0 */
  1. Pointers. A pointer holds the memory address of a value like so:
// 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 fmt.Println(*p) // reads through the pointer p *p = 21 // set i through the pointer p
  1. Go has structs that are collection of fields, we can use them to define in memory data structures (like the name suggests):
type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} v.X = 4 fmt.Println(v.X) } // You can also do shenanigans with pointers to structs // for more you should checkout "A tour of Go"
  1. Arrays are declare like [n]T (this declares an array of n T's):
package main func main() { primes := [6]int{2, 3, 5, 7, 11, 13} // the size of the array is part of it's type // so it can be resized // on the other hand a slice is dynamically sized declared by the type []T var sliced []int = primes[1:4] // it doesn't store any data, it just references the array // so changing the slice also changes the array // slices have both length (len(s)) and capacity (cap(s)) // capacity is the length of the referencing array and len is the length of the slice // we can use the make built in function to create a slice a := make([]int, 5) // creates a slice of length 5 // we can append data to slices using append() a = append(a, 42) // to iterate through a slice or map theres the range form of for for i, v := range primes { fmt.Printf("the %d º prime is %d", i, v) } }
  1. Maps are key to values. The zero value of a map is nil and a nil map have no keys and can't have a key added to it. To initialize a map we use a make function.
package main var test map[string]int func main() { test = make(map[string]int) test['one'] = 1 test['two'] = 2 // alternatively you can use a map literal var literalMap = map[string]int{ "one": 1, "two": 2, } // we can insert and update values as expected // and also delete using the delete() function delete(literalMap, "one") // to test if a value is contained in a map we can do the following el, ok = literalMap["one"] // ok is false, since we've deleted one from the map }
  1. Functions are values and can be passed as arguments.
package main import ( "fmt" "math" ) 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)) fmt.Println(compute(hypot)) fmt.Println(compute(math.Pow)) }

This is it for now. Next I'll be going over methods, interfaces, generics and concurrency. I'm liking go, seems like a productive language to get things done, not in an exciting way, but in a productive(?) way (up to debate). But yeah, I guess that's it for now, until next time.