This guide heavily cites A Tour of Go by Google.
- Created by Google
- Created by luminaries in Computer Science
- Rob Pike (Unix, UTF-8)
- Ken Thompson (B, C, Unix, UTF-8)
- Robert Greisemer (Hotspot, JVM)
Google looked at all other programming languages and decided they needed to make their own; the others would not do.
It is the fastest growing programming language in America. It is also the highest paying programming language in America. Go is used widely in industry.
In 2005/2006 the first dual core processors became available. Up until then, every programming language was built to only take advantage of a single core. Go natively takes advantage of multiple core systems; it was built for parallel computing and concurrency.
In 2009 the language went open source. In 2012 version 1.0, the first stable version, was released.
- Efficient compilation
- Efficient execution
- Ease of programming
- Web services at scale
- Google is rewriting their infrastructure to leverage goal
- Concurrency & Parallelism
- Good for system level automation and command line tools
- Cryptography
- Image processing
Like other languages Go has a number of packages. We can also import specific parts of packages if we know the path to that sub-package:
package main
import (
"fmt" // fmt is a basic I/O package
"math/rand" // rand is a sub-package of math
"time" // Basic time and date package
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
fmt.Println("The time is ", time.Now())
}Note: rand.Intn is deterministic; it will always return the same number. rand.Seed offers a more time dependent option.
This procedure is case sensitive. For example, 'math.pi' will not export 'pi' from math. Instead, we must use 'math.Pi'. Notice the uppercase 'P' in 'Pi'. This is a language requirement. Any unexported names are not accessible from outside the package.
Functions can take 0+ arguments. The variable type comes after the name of the variable:
package main
import "fmt"
// Note: The type comes after the name
// Also, the return type comes after the arguments
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}If 2+ consecutive named function parameters share a type we can omit the type from all but the last parameter:
package main
import "fmt"
// x and y are both int so we only write 'int' once at the end
func add(x, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}We can also return multiple results from a function so long as they are assigned:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}We can set a default named return value in our function declaration. Furthermore, if we use the 'return' keyword without a return value it will default to this named return value. Below, 'sum' is the named return value.
However, it is important to use naked returns sparingly or only in small functions as it may inhibit readability.
package main
import "fmt"
// Here, the return value is named 'sum'.
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // This 'naked' return will default to returning the named return value 'sum'
}
func main() {
fmt.Println(split(17))
}The 'var' keyword indicates declaration. If multiple variables are of the same type we can end the var statement with the variable type for all listed variables:
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}Variables may be initialized. If they are initialized, we do not need to strongly type the variable, as it will inherit the implicit type of the initialized value:
package main
import "fmt"
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!"
fmt.Println(i, j, c, python, java)
}Inside a function the ':=' short assignment statement can be used in place of a var declaration with implicit type. Outside a function every statement must begin with a keyword such as var, func, etc., because the '=:' is only available inside a function.
package main
import "fmt"
func main() {
var i, j int = 1, 2
k := 3
c, python, java := true, false, "no!"
fmt.Println(i, j, k, c, python, java)
}These are the basic explicit types we can use in GoLang:
- 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
package main
import (
"fmt"
"math/cmplx"
)
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
func main() {
fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt)
fmt.Printf("Type: %T Value: %v\n", z, z)
}However, unless there is a specific need, we can stick to the most basic types; e.g. using int over int64, etc.
If a variable has not been initialized it will default to a specific value depending on its type:
- 0 for numerics
- false for booleans
- "" 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)
// In function context short hand:
i := 42
f := float64(i)
u := uint(f)If no explicit type is written, Golang will infer the type using the value on the right most side:
var i int
j := i // j is an int
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128Constants are declared using the 'const' keyword. They can be strings, characters, booleans, or numerics. They cannot be declared using the ':=' short hand syntax as that is only short hand for 'var'; we must explicitly write 'const'.
Numeric constants are high precision values. An untyped constant takes the type needed by its context:
package main
import "fmt"
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}No parentheses, only curly braces:
sum := 0
for i := 0; i < 10; i++ {
sum += i
}The init and post statements are optional:
sum := 1
for ; sum < 1000; {
sum += sum
}The for loop serves as a while loop in Go:
sum := 1
for sum < 1000 {
sum += sum
}And an empty for loop serves as an infinite loop:
for {
// ever and ever and ever ...
}Conditions omit parentheses but include curly braces:
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}We can inject a short statement that executes before the conditional logic, then the condition logic:
func pow(x, n, lim float64) float64 {
// Assign v, then determine if v is less than the limiter value:
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}Here is an 'else' statement with some scope considerations:
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// can't use v here, though
return lim
}In Go, the selected switch case is the only one that is executed:
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)
}
}switch conditions are checked in the order they are written:
func main() {
fmt.Println("When's Saturday?")
today := time.Now().Weekday()
switch time.Saturday {
case today + 0:
fmt.Println("Today.")
case today + 1:
fmt.Println("Tomorrow.")
case today + 2:
fmt.Println("In two days.")
default:
fmt.Println("Too far away.")
}
}A switch without a condition os the same as 'switch true':
func main() {
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.")
}
}'defer' defers the execution of a function until the surrounding function returns. The deferred call's arguments are evaluated immediately but the function is not executed until the function returns. Here, the 'world' print statement is not executed until main() returns:
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
// "hello world"Deferred functions are pushed on to a stack in the order which they are called and deferred:
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}The output for this is "done", "9", "8", ... etc. because the final deferred call is the print of 'i' at the end of the loop.
Go has pointers. A pointer points to a memory address which contains some value. The type '*T' is a pointer to value 'T'. Unlike other uninitialized values, it has a zero value of 'nil', because 0 could potentially point to an actual location in memory.
var p *int
The & operator generates a pointer to its operand:
i := 42
p = &i
In the above example we instantiate a pointer without a set reference. Then, we instantiate an integer value to i, and point 'p' to the memory location of i using the ampersand.
Below, we retrieve the value of i by way of pointer 'p'. Then, we can use pointer 'p' to set a new value to i.
fmt.Println(*p) // read i through the pointer p
*p = 21 // set i through the pointer p
package main
import "fmt"
func main() {
i, j := 42, 2701
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
}Go has no pointer arithmetic like its grandfather C.
Structs are a collection of fields. Below, we have a vertex; a point on a graph. This owns two coordinate values: X and Y. once Vertex v is instantiated we can access each field using dot '.' notation.
A struct literal deonates a newly allocated struct value by listing the values of its fields.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v:= Vertex{1, 2}
v.X = 4
fmt.Println(v.X, ",", v.Y)
p := &v
p.X = 1e9
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 above snippet also features of an example of using pointers to reference a struct. Finally, we see that the uninitialized vertices default to '0' integer values, which is consistent with the basic integer initialization rules covered earlier in this guide.
The type '[n]T' is an array of n values of type T. An array's length is part of its type so arrays cannot be resized. However, Go has tools to manipulate arrays.
primes := [6]int{2, 3, 5, 7, 11, 13}A slice in Go is a dynamically-sized interface for an array. In practice they are more common than Go 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:
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
// Above: Starts at first element, stops at, but does not include, 4th element:
fmt.Println(s) // [3 5 7]
}Slices are like references to arrays. It references a sub array. Changing the elements of a slice modifies the source array:
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}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}The size is inferred by the initialized data.
func main() {
q := []int{2, 3, 5, 7, 11, 13}
fmt.Println(q)
r := []bool{true, false, true, true, false, true}
fmt.Println(r)
s := []struct {
i int
b bool
}{
{2, true},
{3, false},
{5, true},
{7, true},
{11, false},
{13, true},
}
fmt.Println(s)
}The default low and high values are the beginning of the array, '0', and end of the array, 'n + 1'. Given an array of 10 elements this is how we can slice it:
a[0:10]
a[:10]
a[0:]
a[:]Slices have length and capacity. Where s is a slice we can use the methods len(s) and cap(s) respectively.
func main() {
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// Slice the slice to give it zero length.
s = s[:0]
printSlice(s)
// Extend its length.
s = s[:4]
printSlice(s)
// Drop its first two values.
s = s[2:]
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}The output for this would be:
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]
The zero value of a slice is 'nil':
func main() {
var s []int
fmt.Println(s, len(s), cap(s))
if s == nil {
fmt.Println("nil!")
}
}Slices can be created with make(). This is how we create dynamic arrays in Go. The make() function allocates a zeroed array and returns a slice that refers to that array:
a := make([]int, 5) // len(a)=5We may also specify a capacity:
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)=4Here is a more comprehensive example of make():
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}Slices may contain slices:
func main() {
// Create a tic-tac-toe board.
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// The players take turns.
board[0][0] = "X"
board[2][2] = "O"
board[1][2] = "X"
board[1][0] = "O"
board[0][2] = "X"
for i := 0; i < len(board); i++ {
fmt.Printf("%s\n", strings.Join(board[i], " "))
}
}We can use append(...) to add elements to an existing slice:
func append(s []T, vs ...T) []TWe can expand this into an actual use case:
func main() {
var s []int
printSlice(s)
// append works on nil slices.
s = append(s, 0)
printSlice(s)
// The slice grows as needed.
s = append(s, 1)
printSlice(s)
// We can add more than one element at a time.
s = append(s, 2, 3, 4)
printSlice(s)
}The output for the above is:
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4]
We can use the range keyword to iterate over a slice (or a map, discussed in the next section):
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)
}
}When ranging over a slice we return two values:
- The index (i)
- The copy of the element/value at that index (v)
This yields the output:
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128
You may also omit index or range by assigning '_':
for i, _ := range pow
for _, value := range powAnd as a shorthand, if you are only interested in the index, just omit the second variable:
for i := range powA Map is a Dictionary of key: value pairs. The uninitialized, zero value of a map is 'nil'. A 'nil' map cannot be added to. We can use make() in conjunction with map to initialize a map with some data:
type Vertex struct {
Lat, Long float64
}
var m map[string]Vertex
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}Map literals are similar to structs but keys are not required:
type Vertex struct {
Lat, Long float64
}
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
func main() {
fmt.Println(m)
}If a top-level type is just a type name we can omit it from the elements of the literal in this shorthand style:
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}Above, we write 'Vertex' once to indicate the value type, but do not need to repeat 'Vertex' for each literal entry.
Insert or update an element in map m:
m[key] = elemRetrieve an element:
elem = m[key]Delete an element:
delete(m, key)Test that a key is present with a two-value assignment:
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.
Note: If elem or ok have not yet been declared you could use a short declaration form:
elem, ok := m[key]Here is an actual implementation:
func main() {
m := make(map[string]int)
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}And the output:
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
Functions are values too. They can be passed like other values. Function values may be used as function arguments and return values:
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))
}Go functions support closures. Closures can use data from outside its scope but from outside this scope we cannot reach into the closure:
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),
)
}
}WIP
WIP