STUFF (tour)
- explain early that functions are not polymorphic (basics section)
- earlier: type aliasing (which introduces a new type, not just an alias, except for byte and rune)
- swap first two pages of Methods section
- I would move closures later (possibly after methods)
Welcome
- “go tool tour” doesn't work on Fedora, why not?
- offline tour runs
${GOPATH}/bin/gotour
- “package main” for commands
Packages
Basic packages
* Every Go program is made up of packages, starts at main
package.
* By convention, the package name is the same as the last element of the import path.
* import “math/rand”
imports from math/rand/rand.go
:
package rand import ( "math" )
* “factored” import statement:
import ( "fmt" "math" )
Exported stuff
* name is exported if it begins with a capital letter:
func main() { fmt.Println(math.Pi) }
* From math/const.go
:
// Mathematical constants. const ( E = 2.71828182845904523536028747135266249775724709369995957496696763 // https://oeis.org/A001113 Pi = 3.14159265358979323846264338327950288419716939937510582097494459 // https://oeis.org/A000796 Phi = 1.61803398874989484820458683436563811772030917980576286213544862 // https://oeis.org/A001622 ... )
Functions
Basic functions
func add(x int, y int) int { return x + y }
Collapse return types
func add(x, y int) int { return x + y }
Multiple return values
func swap(x, y string) (string, string) { return y, x } func main() { a, b := swap("hello", "world") fmt.Println(a, b) }
Named return values
func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return } func main() { fmt.Println(split(17)) }
Naked return statements should be used only in short functions, as with the example shown here. They can harm readability in longer functions.
Variables
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
Declarations
Type of list of variables is at the end:
var c, python, java bool
Initializers
var i, j int = 1, 2
Inside functions, implicit short declarations using :=
:
func main() { var i, j int = 1, 2 k := 3 c, python, java := true, false, "no!" fmt.Println(i, j, k, c, python, java) }
Can be “factored” into blocks:
package main import ( "fmt" "math/cmplx" ) var ( ToBe bool = false MaxInt uint64 = 1<<64 - 1 z complex128 = cmplx.Sqrt(-5 + 12i) )
Uninitialized variables are given their corresponding zero value: * 0 for numeric types, * false for the boolean type * “” (the empty string) for strings
Type conversions
No implicit type conversion, must be explicit:
var i int = 42 var f float64 = float64(i) var u uint = uint(f) i := 42 f := float64(i) u := uint(f)
Print the type of a variable:
func main() { v := 42.1 // change me! fmt.Printf("v is of type %T\n", v) }
Constants
const Pi = 3.14
Flow control
for loop
Go's only looping construct:
func main() { sum := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
for ; sum < 1000; { sum += sum }
Here's Go's while loop:
for sum < 1000 { sum += sum }
Go's infinite loop:
for { }
if
func sqrt(x float64) string { if x < 0 { return sqrt(-x) + "i" } return fmt.Sprint(math.Sqrt(x)) }
Open with a short initializer statement:
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } return lim }
Variables declared inside an if short statement are also available inside any of the else blocks.
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 }
switch
Go's switch cases need not be constants, and the values involved need not be integers.
Top to bottom, executes only one possibility:
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.", os) } }
Switch with no condition same as switch true
– cleaner to write if-then-else:
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
func main() { defer fmt.Println("world") fmt.Println("hello") }
Multiple defers are stacked so executed in reverse order:
func main() { fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done") }
Pointers
Go has no pointer arithmetic; zero value is nil
.
Declaring simple pointers:
var p *int
Initialize at the same time with :=
:
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 }
Structs
Structs
type Vertex struct { X int Y int } func main() { fmt.Println(Vertex{1, 2}) }
Struct fields
type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} v.X = 4 fmt.Println(v.X) }
Pointers to structs
type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} p := &v p.X = 1e9 // no need to dereference fmt.Println(v) }
Struct literals
type Vertex struct { X, Y int } 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 ) func main() { fmt.Println(v1, p, v2, v3) }
Arrays
Simple declaration (arrays cannot be resized):
func main() { var a [2]string a[0] = "Hello" a[1] = "World" fmt.Println(a[0], a[1]) fmt.Println(a) primes := [6]int{2, 3, 5, 7, 11, 13} fmt.Println(primes) }
Slices
Basic declaration
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
:
func main() { primes := [6]int{2, 3, 5, 7, 11, 13} var s []int = primes[1:4] fmt.Println(s) }
Slices are simply references to arrays
A slice does not store any data, it just describes a section of an underlying array.
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.
Slice literals
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) }
Slice defaults
a[0:10] a[:10] a[0:] a[:]
Slice length and 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)
.
Nil slices
func main() { var s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil!") } }
Creating a slice with 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) }
Slices of 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], " ")) } }
Appending to a slice
Range
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) } }
func main() { pow := make([]int, 10) for i := range pow { pow[i] = 1 << uint(i) // == 2**i } for _, value := range pow { fmt.Printf("%d\n", value) } }
Maps
Function closures (I would do this later)
func adder() func(int) int { sum := 0 // closure part 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), ) } }
Fibonacci closure:
func fibonacci() func() int { current, next := 0, 1 return func() int { current, next = next, next+current return current } }
Methods
Non-method Abs() function
Non-method Abs()
function for Vertex
, which takes a Vertex
as a regular argument (no polymorphism)
type Vertex struct { X, Y float64 } func Abs(v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(Abs(v)) }
Basic method
Go does not support classes; rather, you can define methods on types by adding a receiver argument to a function:
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { // receiver argument return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) // style 1 fmt.Println(Vertex{3,4}.Abs()) // style 2 }
Methods and type "aliasing"
You can declare a method on non-struct types, too. Below, we see a numeric type MyFloat
with an Abs
method:
type MyFloat float64 // introduces a new type, not just an alias func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } func main() { f := MyFloat(-math.Sqrt2) fmt.Println(f.Abs()) }
You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int
).
Methods with pointer receivers
Refresher: call-by-value
Call-by-value function, which makes a copy of the argument:
package main import ( "fmt" ) type Vertex struct { X, Y float64 } func Scale(v Vertex, f float64) Vertex { v.X = v.X * f v.Y = v.Y * f return v } func main() { v := Vertex{3, 4} fmt.Println(v) fmt.Println(Scale(v, 10)) fmt.Println(v) } {3 4} {30 40} {3 4}
Refresher: pointer-based function
Pointer-based call:
package main import ( "fmt" ) type Vertex struct { X, Y float64 } func Scale(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} fmt.Println(v) Scale(&v, 10) fmt.Println(v) } {3 4} {30 40}
Notes about the above:
- Even if the arg is a pointer, Go allows you to use “.” to dereference.
- You cannot support both variations of that function