Go (golang) ======================================== (Created July 2016, updated 2020.) Learn Go ---------------------------------------- 1. [Install Go](https://golang.org/doc/install). 2. Take [a tour of Go](https://tour.golang.org/welcome/1). 3. Read Donovan and Kernighan's book [The Go Programming Lanuage](https://www.informit.com/store/go-programming-language-9780134190440). Update Go ---------------------------------------- Download a binary release from [https://golang.org/dl/](https://golang.org/dl/), then: ``` $ sudo rm -r /usr/local/go/ $ sudo tar -C /usr/local -xzf ~/Downloads/go1.14.6.linux-amd64.tar.gz ``` Hello, world ---------------------------------------- ``` $ mkdir -p $GOPATH/src/example.com/me/hello $ cd $GOPATH/src/example.com/me/hello $ vi hello.go package main import ( "fmt" ) func main() { fmt.Println("Hello, world!") } $ go vet $ go fmt $ go run Hello, world! $ go build $ ./hello Hello, world! $ go install $ $GOPATH/bin/hello Hello, world! ``` Documentation ------------------------------------------------------------------------ [https://golang.org/doc/](https://golang.org/doc/) ``` $ go doc fmt | grep 'uint' uint, uint8 etc.: %d, %#x if printed with %#v $ go doc fmt Println $ go doc -src fmt Println ``` Show the docs for the package in the current directory: ``` $ go doc . ``` Comments (and `go doc` Commentary) ------------------------------------------------------------------------ Go's `go doc` tool [generates documentation from comments](https://golang.org/doc/effective_go.html#commentary). ``` // Copyright 2020 Paul Gorman. All rights reserved. // The MIT license governs use of this software; see the LICENSE file. // Package hello provides a friendly greeting. // (A comment at the head of the package gets used by 'go doc' to auto-generate docs.) package hello // Hello returns a greeting string. // (A comment proceeding a function declaration gets used by 'go doc' to auto-generate docs.) func Hello() string { return "Hello, world!" } // Double front-slashes denote a comment that runs to the end of the line. /* A slash and a splat starts a multi-line comment that ends with a splat and a slash. */ ``` Types ---------------------------------------- - Signed integer types: `int8`, `int16`, `int32`, and `int64` - `rune` is an alias to `int32`. (A `rune` is like a Unicode code point.) - Unsigned integer types: `uint8`, `uint16`, `uint32`, and `uint64`. - `byte` is an alias to `uint8`. - Integer aliases (size varies by architecutre): `uint`, `int`, and `uintptr`. - Floating-point numbers: `float32` and `float64`. - Complex numbers: `complex64` and `complex128`. - `string` can be used like a read-only slice of bytes. - Remember that some characters may be multi-byte. - String literals: "double quotes" or `` backticks. - Go interprets escapes like "\n" in double-quotes but not in backticks. - `boolean` for true and false (effectively a 1-bit integer). - Functions are a first-class type that can be passed as arguments, returned as values, or used as a fields in structs. - See below for slices, maps, and structs. Variables ---------------------------------------- - Variable names begin with a letter, and may include letters, numbers, or underscores. - Use camelCase/BumpyCase. - Lower-case names are local to a package. Upper-case names are exported outside the package. - Declared but unassigned variables get a default value — zero for numeric types, false for booleans, "" (empty) for strings. - Go lexically scopes variables to blocks (i.e., they exist within the bounds of curly braces). ``` // Declare variables then assign values var x int x = 100 // …or declare and assign in one statement: var y int = 10 // Using the `:=` operator in a declaration infers type: x := 100 // Subsequent assignments to already-declared `x` must use the `=` operator: x = 2 // For multiple variables, as long as one is newly declared, `:=` works: x, y, z := 9, "UFO", 42 // A constant: const pi float32 = 3.14159 ``` For ---------------------------------------- ``` // `for` is Go's only loop. for i := 0; i < 10; i++ { sum += i } // Omitting the loop's initialization and post-iteration statements makes a `while` loop: sum := 1 for sum < 1000 { sum += sum } // Loop forever by omitting even the loop's test statement: for { foo() } // Iterate over a slice: sl := []string{"a", "b", "c", "d"} for index, value := range sl { fmt.Println(index, " : ", value) } // Note that `range` gives a _copy_ of the value. // Changes made to the copy don't affect the original value outside the loop. // To change the original, reference it by index, like origSlice[i]. sl := []string{"a", "b", "c", "d"} for index, value := range sl { value = "x" // `sl` is unchanged. sl[index] = "x" // `sl` changed. } ``` - The `continue` keyword jumps to the next iteration of the loop. - The `break` keyword terminates the loop. If ---------------------------------------- ``` // `if` can start with multiple short statements. // `x` is scopped to the `if` block. if x := blerg(); x < 0 { return x + 1 } else if y == 99 { return y } else { return x - 1 } ``` Switch ---------------------------------------- Each `case` in a `switch` breaks implicitly (unless it ends with `fallthrough`). Like `if`, 'switch' can have multiple short preliminary statements. ``` fmt.Print("Go runs on ") switch os := runtime.GOOS; os { case "darwin": fmt.Println("macOS.") case "linux": fmt.Println("Linux") default: fmt.Printf("%s.", os) } ``` Without a condition, `switch` acts like `switch true`. This can be used for long if-then-else chains. Functions ---------------------------------------- ``` // Functions can return multiple values: func vaporize(name string) (int, bool) { // … } // Discard uninteresting return values by assigning them to the magic underscore: _, b = vaporize("bar") // Go has "function literals" (a.k.a. anonymous functions). // Function literals are closures. // They share variables defined in their surrounding function. // Those shared variables survive as long as they are accessible. f := func(x, y int) int { return x + y } // Define new function types: type Add func(a int, b int) int ``` ### Defer Go's `defer` keyword delays execution of a function until return of the enclosing function. `Defer` mainly helps with "clean-up" tasks. Arguments to deferred functions are evaluated immediately (not when the surrounding function returns). Deferred function calls get pushed onto a stack. When a function returns, its deferred functions get called in last-in-first-out order. ``` func main() { file, err := os.Open("myfile") if err != nil { fmt.Println(err) return } defer file.Close() // … Read the file, etc. } ``` ### Variadic Functions A variadic function can be called with a varying number of arguments. ``` /// Prepend the final parameter type with an ellipsis: func sum(vals ...int) int { total := 0 for _, val := range vals { total += val } return total } sum(1, 2, 3, 4) // Invoking a variadic function with a slice argument: values := []int{1, 2, 3, 4} sum(values...) ``` Strings ---------------------------------------- ``` // Raw string literals are useful for defining regular expressions, // or embedding templates in code, like a Perl heredoc. x := `This is a raw string literal.` // Convert a string to a byte array: s := "Keep off the moors!" bytearray := []byte(s) // Convert a byte array to a string: s2 := string(bytearray) // The `+=` operator concatenates strings. s += " bar" // However, `+=` creates a new string. // For large or repeated uses, use `Join` from the `strings` package: strings.Join(os.Args[1:], " ") ``` From section "3.5.4. Strings and Byte Slices" of _The Go Programming Language_: > Because strings are immutable, building up strings incrementally can involve a lot of allocation and copying. > In such cases, it’s more efficient to use the `bytes.Buffer` type[…] new() and make() ---------------------------------------- Go had two allocation primitives: `new` and `make`. They work on different types, and do slightly different things. `new` is a built-in function. It allocates memory. However, rather than _initialize_ that memory, `new` only _zeroes_ it. So, `new(T)` allocates and zeroes enough memory to hold a value of type `T`. `new` returns a pointer to this newly-allocated, zero-value `T`. Where possible, design data structures usable with the zero-value of each type, without further initialization. For example, the zero-value of `bytes.Buffer` give an empty `Buffer` ready for use. Unlike `new`, the built-in function `make` works only on slices, maps, and channels. Also unlike `new`, `make(T, args)` return an _initialized_ (non-zeroed) value of type `T`. Go uses `make` for these types because slices, maps, and channels always require initialization before use. ``` var p *[]int = new([]int) // This allocates a slice structure. Since `*p == nil`, this is rarely useful. var v []int = make([]int, 100) // The slice `v` now refers to a new array of 100 `int`s. // Unnecessarily complex: var p *[]int = new([]int) *p = make([]int, 100, 100) // Idiomatic: v := make([]int, 100) ``` Pointers ---------------------------------------- Go passes by value. When passing an argument to a function, Go creates a copy of the value that lives for the scope of the function. Changes made inside the function only affect the copy. By passing a pointer to a function, changes to the value survive outside the function. - Like in C, `&` is the "address-of" operator, and `*` is the "value-at" operator. - The `&` operator returns a pointer to its operand; `&T` returns a pointer to `T`. - The `*` operator returns the value of its operand; `*T` returns the value of `T`. ``` // `new` allocates memory and initializes it to a zero value. x := new(int) // `y` points to int-size memory, but the value there remains `nil`. var y *int fmt.Println(x) // 0xc420016100 fmt.Println(*x) // 0 fmt.Println(&y) // 0xc420084018 fmt.Println(y) // Nil ``` ### Is passing a pointer more efficient than copying a value? Passing a pointer is probably more efficient for large structs, but pass simple scalar values (ints, slices, etc.) as copies. Passing simple values as pointers invites inefficiencies from cache misses and heap allocations. https://www.reddit.com/r/golang/comments/a410gl/go_is_passbyvalue_does_that_mean_each_function/ebb12dw/ Stucts ---------------------------------------- ``` type Car struct { Model string Wheels int Speed int } mycar := Car{ Model: "Beetle", Wheels: 4, // Note *required* trailing comma! } secondcar := Car{} secondcar.Wheels = 3 thirdcar := &Car{"Hexamobile", 6, 0} // Pointer to new Car. ``` Go structures can receive methods, making them a bit like objects. ``` func (c *Car) Accelerate() { // *Car is the receiver of the Accelerate() method. c.Speed += 10 } thirdcar.Accelerate() ``` (Any Go type can receive methods.) Go structs support embedded types. These are awkward to use in some ways, but let us call methods of the embedded type on the parent struct. ``` type Dunebuggy struct { Car Grooviness int } // … b := Dunebuggy{ Car: Car{Wheels: 4,}, Grooviness: 19, } fmt.Println(b.Wheels) b.Accelerate() fmt.Println(b.Speed) ``` Anonymous structs: ``` car := struct { make string model string color string weight float64 speed float64 }{ "Ford", "Destroyer", "orange", 3628.7, 227.4, } fmt.Println(car.speed) ``` Use struct tags to appease JSON consumers that want lowercase key names: ``` type Animal struct { Species string `json:"species"` Color string `json:"color"` } ``` ### Composition Instead of hierarchical inheritance, Go supports composition. Compose hybrid types by including one struct type in another. (Some languages call this a "mix-in" or "trait".) ``` type Vehicle struct { Make string Model string } type Bicycle struct { *Vehicle Wheels int Riders int } func (b *Bicycle) String() string { return fmt.Sprintf( "Make: %s, Model: %s, Wheels: %d, Riders: %d", b.Vehicle.Make, b.Model, // Go exposes both "b.Vehicle.Model" and the shorter "b.Model". b.Wheels, b.Riders, ) } func main() { mybike := &Bicycle{ Vehicle: &Vehicle{"Raleigh", "Tourist"}, Wheels: 2, Riders: 1, } fmt.Println(mybike) } ``` Overloading is the ability to create multiple methods with the same name differentiated either by type of argument or number of arguments. Go does not support overloading. However, we can overwrite the methods of a mixed-in type (and if necessary still call an overwritten method like `thing.Mixin.foo()`). ### Factories not Constructors ### Go doesn't have constructors for objects, but the factory pattern helps create new structs. ``` type Car struct { Model string Wheels int Speed int } func NewCar(model string, wheels int, speed int) *Car { return &Car{ // We return a pointer to the new struct. Model: model, Wheels: wheels, Speed: speed, } } func main() { monsterTruck := NewCar("Crusher", 4, 1000000) fmt.Println(monsterTruck.Speed) } ``` ### Default Function Arguments ### https://talks.golang.org/2012/splash.article > One feature missing from Go is that it does not support default function arguments. This was a deliberate simplification. Experience tells us that defaulted arguments make it too easy to patch over API design flaws by adding more arguments, resulting in too many arguments with interactions that are difficult to disentangle or even understand. The lack of default arguments requires more functions or methods to be defined, as one function cannot hold the entire interface, but that leads to a clearer API that is easier to understand. Those functions all need separate names, too, which makes it clear which combinations exist, as well as encouraging more thought about naming, a critical aspect of clarity and readability. Arrays ---------------------------------------- In most cases, use a slice instead of an array. The size of an array never changes after creation. ``` var counts [10]int // Create an array counts[0] = 102 // Assign a value to an array. weights := [3]int{3492, 487, 48762} // Create and initialize an array for index, value := range weights { // Iterate over an array. // … } ``` Slices ---------------------------------------- Slices are an abstraction on top of arrays, providing growable lists. - An array has a length (findable with the `len` function). - A slice has both a length and a capacity (findable with the `cap` function). - The slice **length** is the current number of elements allocated. - The slice **capacity** is the size of the array underlying the slice. ``` weights := []int{234, 3458435, 223, 23432} // Create and populate a slice. weights := make([]int, 10) // Create a slice with length 10 and capacity 10 (values initialized to "0"). weights := make([]int, 0, 10) // Create a slice with length 0 and capacity 10. ``` If we force Go to grow the capacity of a slice with `append()`, it automatically copies all the values into a larger array. Go uses a two-times algorithm — each time it grows the slice, the new array doubles the size of the old one. ``` a := make([]int, 5) fmt.Println(len(a), cap(a)) // 5 5 a = append(a, 1) fmt.Println(len(a), cap(a)) // 6 10 for i := 0; i < 5; i++ { a = append(a, 1) } fmt.Println(len(a), cap(a)) // 11 20 for i := 0; i < 10; i++ { a = append(a, 1) } fmt.Println(len(a), cap(a)) // 21 40 ``` Go accepts slice notation, (although not negative notation, like `x[:-2]`). ``` x := []int{2, 4, 8, 12, 16, 20, 24} fmt.Println(x[2:5]) // 8 12 16 fmt.Println(x[:3]) // 2 4 8 fmt.Println(x[5:]) // 20 24 y := make([]int, 3) copy(y, x[3:6]) // Copy part of one slice to another. fmt.Println(y) // 12 16 20 ``` The `copy` function creates entries in a destination slice to match the source slice, overwriting any existing destination entries: ``` s1 := []int{0, 1, 2, 3} s2 := make([]int, 4) copy(s2, s1) // Both slices now hold [0 1 2 3]. ``` Maps ---------------------------------------- Maps hold keys and values (like dictionaries in Python). Initialize an unpopulated map with `make`, like: ``` m = make(map[string]int) ``` Maps grow dynamically, but `make` accepts an optional second argument for initial length. The `delete` function removes items from the map. ``` ages := make(map[string]int) ages["Ben"] = 26 ages["Richard"] = 31 fmt.Println(ages["Ben"]) fmt.Println(len(ages)) // 2 delete(ages, "Ben") fmt.Println(len(ages)) // 1 codenames := map[string]string { // Another way to declare and initialize maps. "Ben": "Brown Boxer", "Richard": "Steam Shovel", "Phil": "Panther", "Anne": "Seatle", } for key, value := range codenames { fmt.Println(key, "\t:\t", value) } type Monstertruck struct { Name string Tirepressure map[string]int } mytruck := &Monstertruck { Name: "King Krusher", Tirepressure: map[string]int { "Front-left": 100, "Front-right": 99, "Rear-left": 100, "Rear-right": 97, }, } fmt.Println(mytruck.Tirepressure["Rear-right"]) ``` Writing to a `nil` map causes a runtime panic. ``` // A map retrieval yields a zero value when the key is not present: if capitals["Michigan"] != "" { fmt.Println(capitals["Michigan"]) } // …or, check for a second "ok" value to test the result of a map lookup: if city, ok := capitals["Michigan"]; ok { fmt.Println(city, ok) // -> "Lansing" true } ``` When iterating over a map with a range loop, the order is not guaranteed to be the same from one run to the next. Maps are not safe for concurrent use. Protect maps with `sync.RWMutex`. ``` var counter = struct{ sync.RWMutex m map[string]int }{m: make(map[string]int)} // Lock to to read: counter.RLock() n := counter.m["some_key"] counter.RUnlock() fmt.Println("some_key:", n) // Lock to to write: counter.Lock() counter.m["some_key"]++ counter.Unlock() ``` Error Handling ---------------------------------------- ``` f, err := os.Open("filename.ext") if err != nil { log.Fatal(err) } ``` Because error messages are often chained together, when writing error messages: - Don't capitalize like a sentence. - Don't add line breaks. - `f(x)` should return errors that include the error's context within `f` and the value of `x`. ### Panic and Recover https://blog.golang.org/defer-panic-and-recover > **Panic** is a built-in function that stops the ordinary flow of control and begins _panicking_. When the function F calls panic, execution of F stops, any deferred functionsain F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic. The process continues up the stack until all functions in the current goroutine have returned, at which point the program crashes. Panics can be initiated by invoking panic directly. They can also be caused by runtime errors, such as out-of-bounds array accesses. > **Recover** is a built-in function that regains control of a panicking goroutine. Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution. ### Error Writing Strategies Section 5.4 of _The Go Programming Language_ describes five strategies of error writing. 1. **propagate** the error from a subroutine up to the calling routine. 2. **retry** some number of times, possibly waiting between each attempt. This makes sense for transient problems or delays that may resolve themselves. 3. Log the error and gracefully **end the program**. Try to do this only from the `main` package. 4. Log the error and **continue**, possibly with reduced functionality. 5. Choose to safely **ignore** some errors. Very simple functions, where only one thing might go wrong, can return a boolean value rather than an error. ``` resp, err := http.Get(url) if err != nil { return nil, err } // Add context to an error before propagating it upward: doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { // `fmt.Errorf` acts like `fmt.Sprintf`, but returns an `error` type value. // The `%w` verb in `Errorf` allows use of `errors.Unwrap`, `errors.Is`, and `errors.As`. return nil, fmt.Errorf("parsing %s as HTML: %w", url, err) } // Die (strategy 3): log.Fatalf("Bad juju: %v", err) // Return a boolean rather than an error (strategy 5): value, ok := cache.Lookup(key) if !ok { // … cache[key] does not exist. } ``` ### Errors package The [errors package](https://golang.org/pkg/errors/) provides new functions for writing a library that exposes customized error types (or consuming a library that exposes helpfully typed errors): - `new` creates a new error with the argument text (`errors.New("still loose")`). - `is`. Do `if errors.Is(err, io.ErrUnexpectedEOF)` instead of the old `if err == io.ErrUnexpectedEOF`. - `as`. Use `as` instead of `is` when checking error type with a type assertion or type switch. - `unwrap`. Let's a custom error type define an Unwrap method to return the error value. - And `fmt.Errorf` gains the `%w` verb to wrap earlier errors (`fmt.Errorf("while tightening: %w", err)`). ``` var errEmptyRow = errors.New("empty row") // <------------ var errHeaderRow = errors.New("header row") func readRow(s string) (payment, error) { var p payment if reEmptyLine.MatchString(s) { return p, errEmptyRow // <------------ } if reLockboxHeader.MatchString(s) { return p, errHeaderRow } … return payment, err } func readFiles(files []string) ([]payment, error) { payments := make([]payment, 0, 100) for _, f := range files { file, err := os.Open(f) if err != nil { log.Fatal(err) } defer file.Close() line := 0 scanner := bufio.NewScanner(file) for scanner.Scan() { line++ p, err := parseLockboxRow(scanner.Text()) if err != nil { if errors.Is(err, errHeaderRow) || errors.Is(err, errEmptyRow) { // <------------ continue } return nil, fmt.Errorf("readFiles found bad line %d in file %s: %v", line, f, err) } payments = append(payments, p) } … } return payments, nil } ``` Packages ---------------------------------------- - Uppercase names expose functions, variables, and types visible outside the package. - Lowercase names hide functions, variables, and types outside the package. ``` package main import ( "fmt" "math" "os" // Alias package name, so we can use it here like `m.Pi`: m "example.com/math" // Import only for side-effects (i.e., triggering its `init` function on import): _ "github.com/mattn/go-sqlite3" ) ``` `go get` pulls remote dependencies, and stores them in the Go workspace: ``` $ go get example.com/paulgorman/example ``` It can update one package or all previously downloaded packages: ``` $ go get -u example.com/paulgorman/example $ go get -u ``` Modules -------------------------------- See also https://blog.golang.org/go1.13-errors. ``` $ mkdir mymodule $ cd mymodule $ go mod init example.com/me/mymodule go: creating new go.mod: module example.com/me/mymodule $ go help modules | less ``` The module name need not correspond to a real, existing path as long as it's unique. Interfaces ---------------------------------------- Interfaces do a lot of work in Go. An interfaces specifies the type of an object by its behavior — if something can *do this*, it can be used *here*. Interfaces involve two things: "interface types" and "interface values". An interface type lists the methods a concrete type needs in order to be considered an instance of that interface type. ``` type car interface { accelerate(i int) error brake() error } ``` An interface type establishes a contract based on its methods, but does not specify an implementation for the methods. A concrete type that wants to fulfill the interface contract is responsible for implementing the methods. Anything that implements the methods of an interface type can be treated as that type. For example, a `dunebuggy` type that implements both `accelerate` and `brake` methods can be passed to an `oilchange` function that accepts `car` types. Go's `Fprintf`, for example, outputs to anything with a `Write` method. ``` type geometry interface { area() float64 perim() float64 } ``` The interface lets us write a function that works on different types, so long as they all implement the interface's methods: ``` type rect struct { width, height float64 } type circle struct { radius float64 } func (r rect) area() float64 { return r.width * r.height } func (r rect) perim() float64 { return 2*r.width + 2*r.height } func (c circle) area() float64 { return math.Pi * c.radius * c.radius } func (c circle) perim() float64 { return 2 * math.Pi * c.radius } func measure(g geometry) { fmt.Println(g) fmt.Println(g.area()) fmt.Println(g.perim()) } func main() { r := rect{width: 3, height: 4} c := circle{radius: 5} measure(r) measure(c) } ``` In a loose analogy, if concrete types like `int` and `string` are nouns, and functions are verbs, then interfaces are adjectives. `rect` and `circle` are "geometry-ish". ``` var x geometry r2 := circle{radius: 9} x = r2 x = rect{width:2, height:5} ``` Interfaces with only one or two methods are common in Go. The method often names the interface, like `io.Writer` for something that implements `Write`. A type can implement multiple interfaces. Because interfaces decouple the definition from the implementation, definitions can appear in one package and implementations in a different package. Interfaces can be used as fields in structures, passed as arguments to functions, and so forth. ### The Empty Interface The interface type that specifies zero methods is known as the empty interface (`interface{}`). An empty interface may hold values of any type. Empty interfaces are used by code that handles values of unknown type, like `fmt.Printf`. ``` func describe(i interface{}) { fmt.Printf("(%v, %T)\n", i, i) } func main() { var i interface{} describe(i) // (, ) i = 42 describe(i) // (42, int) i = "hello" describe(i) // (hello, string) } ``` ### Type Assertions ``` t := x.(T) ``` This statement asserts that `x` holds a value of type `T` and assigns the underlying `T` value to the variable `t`. If `x` does not hold a `T` type value, the statement triggers a panic. To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded. ``` t, ok := x.(T) ``` If `x` holds a `T`, then `t` will be the underlying value and `ok` will be true. If not, `ok` will be false and `t` will be the zero value of type `T`, and no panic occurs. (Using `t` after a failed assertion panics, so check the value of `ok` first!) ### Type switches ``` func do(i interface{}) { switch v := i.(type) { case int: fmt.Printf("Twice %v is %v\n", v, v*2) case string: fmt.Printf("%q is %v bytes long\n", v, len(v)) default: fmt.Printf("I don't know about type %T!\n", v) } } func main() { do(21) do("hello") do(true) } ``` The declaration in a type switch has the same syntax as a type assertion `i.(T)`, but the keyword `type` replaces the specific type `T`. ### Interface Example: Stringer ``` package main import "fmt" type IPAddr [4]byte func (a IPAddr) String() string { return fmt.Sprintf("%d.%d.%d.%d", a[0], a[1], a[2], a[3]) } func main() { hosts := map[string]IPAddr{ "loopback": {127, 0, 0, 1}, "googleDNS": {8, 8, 8, 8}, } for name, ip := range hosts { fmt.Printf("%v: %v\n", name, ip) } } ``` ### Interface Example: Error ``` package main import ( "fmt" "time" ) type MyError struct { When time.Time What string } func (e *MyError) Error() string { return fmt.Sprintf("at %v, %s", e.When, e.What) } func run() error { return &MyError{ time.Now(), "it didn't work", } } func main() { if err := run(); err != nil { fmt.Println(err) } } ``` ### Interface Example: Readers https://golang.org/pkg/io/#Reader The `io` package specifies the `io.Reader` interface, which represents the read end of a stream of data: ``` func (T) Read(b []byte) (n int, err error) ``` `Read` populates the given byte slice with data, and returns the number of bytes populated along with an error value. It returns an `io.EOF` error when the stream ends. In one common pattern, an `io.Reader` wraps another `io.Reader`, modifying the stream in some way. For example, the `gzip.NewReader` function takes an `io.Reader` (a stream of compressed data) and returns a `*gzip.Reader` that also implements `io.Reader` (a stream of the decompressed data). ``` package main import ( "io" "os" "strings" ) type rot13Reader struct { rdr io.Reader } func (rot *rot13Reader) Read(p []byte) (n int, err error) { n, err = rot.rdr.Read(p) // We call the "wrapped" io.Reader's Read() to fill p. for i, letter := range p[:n] { switch { case letter >= 'A' && letter <= 'Z': p[i] = 'A' + (letter - 'A' + 13) % 26 case letter >= 'a' && letter <= 'z': p[i] = 'a' + (letter - 'a' + 13) % 26 } } return n, err } func main() { s := strings.NewReader("Lbh penpxrq gur pbqr!") r := rot13Reader{s} io.Copy(os.Stdout, &r) } ``` Concurrency ---------------------------------------- See also https://paulgorman.org/technical/golang-concurency.txt.html Go provides concurrency using goroutines and channels. An OS process has a PID and a block of memory. An OS thread is less expensive than forking an additional process; threads share the memory, open files, and PID of their parent. A goroutine is like a thread scheduled by Go instead of by the OS. Goroutines have much lower overhead than OS threads. Go generally doesn't start a new OS thread for a goroutine. The Go runtime holds a number of threads and multiplexes scheduling goroutines between them; if a goroutine blocks, Go swaps it out to let another goroutine execute on that thread. Goroutines are a lightweight abstraction on top of threads; a Go program doesn't deal with treads, and the OS isn't aware of goroutines. Rob Pike's talk about concurrency is instructive: https://www.youtube.com/watch?v=cN_DpYBzKso Start a goroutine using the `go` keyword. Because goroutines run in the same address space, control access to shared memory. Concurrent reading may be safe, but certainly synchronize whenever concurrently writing values. For simple cases, a mutex may be enough (i.e., `sync.Mutex`). Channels provide another way to safely sharing memory. Go's preference for channels is often stated as: > Don't communicate by sharing memory; share memory by communicating. A channel is a bit like a pipe in a shell, but it has a type. The type matches the kind of data to be passed through the channel. ``` ch := make(chan int) // A channel for int data. func worker(ch chan int) { … } // Pass the channel to a function as type "chan int". ``` Channels support sending and receiving, with direction indicated using arrow notation: ``` ch <- d // Send data to channel. d := <-ch // Receive data from channel. ``` Note that sending data to and receiving data from a channel is blocking; execution of the goroutine pauses until data flows across the channel when the other side is ready. This allows goroutines to synchronize without explicit locks. ``` package main import ( "fmt" "time" "math/rand" ) type Worker struct { id int } func (w *Worker) process(c chan int) { for { data := <-c fmt.Printf("worker %d got %d\n", w.id, data) } } func main() { c := make(chan int) for i := 0; i < 5; i++ { worker := &Worker{id: i} go worker.process(c) } for { c <- rand.Int() time.Sleep(time.Millisecond * 50) } } ``` What if we have multiple channels, and want to read from the one with pending data? Go's `select` statement is like `switch`, but the decision is based on the ability to communicate rather than value tests. `select` lets a goroutine wait on multiple communication channels. If no channel is ready to communicate, `select` falls through to the `default` clause. ``` func fibonacci(c, quit chan int) { x, y := 0, 1 for { select { case c <- x: x, y = y, x+y case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) } ``` We can make a channel buffer. Sends to a buffered channel only blocks when the buffer fills; receives only block when its empty. ``` func main() { ch := make(chan int, 2) ch <- 1 ch <- 2 ch <- 3 // Sending too much to a buffered channel causes a fatal deadlock. fmt.Println(<-ch) fmt.Println(<-ch) } ``` Sometimes channels send events (i.e., the occurrence of the signal is the only thing that's important, not the data sent). Although we can send a tiny `boolean` for the signal, a Go idiom is to use a `struct`. The empty struct emphasizes the "event" nature of the channel. ``` func main() { // [Set up some stuff.] done := make(chan struct{}) go func() { // [Do some stuff.] log.Println("done") done <- struct{}{} // Signal the main goroutine with an empty struct. }() // [Do other stuff.] <-done // Wait the background goroutine to end. } ``` ### Range and Close A sender can close a channel to indicate that no more values will be sent. Receivers test whether a channel has been closed by assigning a second parameter to the receive expression: ``` v, ok := <-ch ``` `ok` is false if there are no more values to receive and the channel is closed. The loop `for i := range c` receives values from the channel repeatedly until it is closed. Only the sender should close a channel, never the receiver, because sending on a closed channel causes a panic. Channels aren't like files; they don't usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming (e.g., to end a `range` loop). ``` package main import "fmt" func fibonacci(n int, c chan int) { x, y := 0, 1 for i := 0; i < n; i++ { c <- x x, y = y, x+y } close(c) } func main() { c := make(chan int, 10) go fibonacci(cap(c), c) for i := range c { fmt.Println(i) } } ``` ### sync.Mutex Channels are great for communication among goroutines, but sometimes they need to share memory. _Mutual exclusion_ is a simple method to avoid conflicts when multiple goroutines access a variable. ``` package main import ( "fmt" "sync" "time" ) // SafeCounter is safe to use concurrently. type SafeCounter struct { v map[string]int mux sync.Mutex } // Inc increments the counter for the given key. func (c *SafeCounter) Inc(key string) { c.mux.Lock() // Lock so only one goroutine at a time can access the map c.v. c.v[key]++ c.mux.Unlock() } // Value returns the current value of the counter for the given key. func (c *SafeCounter) Value(key string) int { c.mux.Lock() // Lock so only one goroutine at a time can access the map c.v. defer c.mux.Unlock() return c.v[key] } func main() { c := SafeCounter{v: make(map[string]int)} for i := 0; i < 1000; i++ { go c.Inc("somekey") } time.Sleep(time.Second) fmt.Println(c.Value("somekey")) } ``` The `<<` and `>>` Operators (left shift and right shift) ---------------------------------------- https://golang.org/ref/spec#Arithmetic_operators > The shift operators shift the left operand by the shift count specified by the right operand. They implement arithmetic shifts if the left operand is a signed integer and logical shifts if it is an unsigned integer. There is no upper limit on the shift count. Shifts behave as if the left operand is shifted n times by 1 for a shift count of n. As a result, x << 1 is the same as x*2 and x >> 1 is the same as x/2 but truncated towards negative infinity. https://stackoverflow.com/questions/5801008/go-and-operators > The super (possibly over) simplified definition is just that << is used for "times 2" and >> is for "divided by 2" - and the number after it is how many times. > So n << x is "n times 2, x times". And y >> z is "y divided by 2, z times". > For example, 1 << 5 is "1 times 2, 5 times" or 32. And 32 >> 5 is "32 divided by 2, 5 times" or 1. Testing ---------------------------------------- See also https://paulgorman.org/technical/golang-testing.txt.html Go includes a lightweight testing framework comprised of the `testing` package and the `go test` tool. Create a test file with a name ending in `_test.go`. The `_test.go` file should contain functions named `TestFOO` with the signature: ``` func (t *testing.T) ``` If the test function calls a failure function like `t.Error` or `t.Fail`, Go considers the test failed. For a package named `stringutil` with a `Reverse` function, create a `stringutil_test.go` file: ``` package stringutil import "testing" func TestReverse(t *testing.T) { cases := []struct { in, want string }{ {"Hello, world", "dlrow ,olleH"}, {"Hello, 世界", "界世 ,olleH"}, {"", ""}, } for _, c := range cases { got := Reverse(c.in) if got != c.want { t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want) } } } ``` … and run the test: ``` $ go test example.com/stringutil ``` Init ------------------------------------------------------------------------ Each source file may define one or more `init` functions (run in the order they appear in the code). Go calls `init` after initialization of global variable declarations and initialization of imported packages. Importing a package runs the package's `init` functions. ``` func init() { if user == "" { log.Fatal("$USER not set") } } ``` Time ------------------------------------------------------------------------ The `time.Format` function uses a reference time: Mon Jan 2 15:04:05 -0700 MST 2006 (i.e., 01/02 03:04:05PM '06 -0700, or 0 1 2 3 4 5 6 7). Git pre-commit hooks ---------------------------------------- ``` $ touch ~/repo/go/src/example.com/myproject/.git/hooks/pre-commit $ chmod a+x ~/repo/go/src/example.com/myproject/.git/hooks/pre-commit $ cat << 'EOF' > ~/repo/go/src/example.com/myproject/.git/hooks/pre-commit #!/bin/sh go fmt ./... go vet ./... go test -cover ./... find -type f -name '*.go' -exec wc -l {} + find -type f -name '*.go' -exec sed '/^\s*$/d' {} + | wc -l; echo ' ↳ total (non-blank) lines of code' EOF ``` Building and Build Flags ---------------------------------------- When building binaries for distribution, we may want to strip debug symbols to reduce the binary size: ``` % go build -ldflags '-s -w' ``` To build a static binary, which can be useful when moving it to a musl system: ``` 5 CGO_ENABLED=0 go build -a -installsuffix cgo ``` While `go build` compiles the executable and writes it to the same directory, `go install` does a little more. `go install` moves the compiled binary to `$GOPATH/bin/`, and does some caching of imported packages. ### Cross-Compiling ``` $ GOOS=windows GOARCH=amd64 go build $ GOOS=openbsd GOARCH=386 go build $ GOOS=linux GOARCH=386 go build $ GOOS=freebsd GOARCH=arm64 go build ``` Other Go Tooling ------------------------------------------------------------------------ ``` $ go env GOARCH="amd64" GOBIN="" GOCACHE="/home/paul/.cache/go-build" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOOS="linux" […] $ go list -m all example.org/inventory github.com/bvinc/go-sqlite-lite v0.5.0 $ go mod why -m github.com/bvinc/go-sqlite-lite # github.com/bvinc/go-sqlite-lite example.org/inventory github.com/bvinc/go-sqlite-lite/sqlite3 $ go mod tidy $ go mod verify $ go clean -modcache $ go doc -src strings.Replace $ go test . # Run all tests in the current directory $ go test ./... # Run all tests in the current directory and sub-directories $ go test ./foo/bar # Run all tests in the ./foo/bar directory $ go test -race ./... $ go test -cover ./... $ go vet ./... $ go fix ./... ```