The Go Programming Language

Getting started

  • go run runs a small Go program.

  • go build compiles a program and outputs an executable

  • go build -o hello_word hello.go lets you choose the output executable.

  • go install <package_name>@version installs a package globally and installs the binary in $GOPATH/bin.

  • go install <package_name>@latest install the latest version of a package.

  • go fmt formats your code to match the standard format.

  • goimports is an extra package that cleans up your import statements.

  • golint is an extra package that helps with style guidelines.

  • go vet detects errors such as assigning values to never used variables.

  • golangci-lint is an extra package that runs many linters together.

Types

Go always assign a default zero value to any declared value. A literal in Go refers to writing out a number, character, string or a rune. Type conversion is achieved with {type}(value), like float64(value).

Variables can be declared with var x int = 10 or x := 10. You can also use a declaration list to declare multiple variables:

var (
  x    int
  y           = 20
  d, e string = "hello"
)

Exception: := cannot be used outside of functions. const can be used to declare immutable variables at the package level.

Arrays are defined with var x [3]int. Values can be read with x[0]. Most of the time a slice is what you should use. On slices the length is not part of its type: var x = []int{10, 20, 30} len returns the length of a slice. append grows a slice. cap returns the capacity of the slice. To create empty slices, use make:

x := make([]int, 5)
x = append(x, 10)

// slicing slices. they are not copies - they share memory
y := x[:2]
z := x[1:]
d := x[1:3]

// copying slices
x := []int{1, 2, 3, 4}
y := make([]int, 4)
num := copy(y, x)

String values can also be extracted using index expressions, but this has unexpected results with some unicode code points.

var s string = "Hello there"
var b byte = s[6]

Maps can be declared with:

totalWins := map[string]int{}

// or initialized with
teams := map[string][]string {
    "Orcas": []string{"Fred", "Ralph", "Bijou"},
    "Lions": []string{"Sarah", "Peter", "Billie"},
    "Kittens": []string{"Waldo", "Raul", "Ze"},
}

// or with make
// the map still have a length of 0 can grow past the initially specified size

ages := make(map[int][]string, 10)

// change a value
totalWins["Orcas"] = 1

// read a value
fmt.Println(totalWins["Orcas"])

// The comma ok Idiom returns a value or nil, and ok: bool if it exists
v, ok := m["hello"]
fmt.Println(v, ok)

// deleting from a map
delete(m, "hello")

Structs are Go’s main data structure (almost like a class).

type Person struct {
    name string
    age  int
    pet  string
}

// there are many ways to initialize a struct
var fred Person

bob := Person{}

julia := Person{
    "Julia",
    40,
    "cat",
}

beth := Person{
    age:  30,
    name: "Beth",
}

// values can be accessed with the dot notation
ftm.Println(beth.name)

// we can also declare anonymous structs
var person struct {
    name string
    age  int
    pet  string
}
person.name = "bob"
person.age = 50

Blocks, shadows, and control structures

Blocks are defined by braces {} and define an scope. A shadowing variable is a variable that has the same name as a variable in a containing block. Shadowed variables can cause bugs, but can be detected by some linters.

// if, else if, else
if n == 0 {
    // ...
} else if n > 0 {
    // ...
} else {
    // ...
}

// scoping a variable to an if statement
if n := rand.Intn(10); n == 0 {
    fmt.Println("That's too low")
}

// complete, C-style for
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// condition only for
for i < 100 {
    fmt.Println(i)
    i = i * 2
}

// infinite loop
for {
    fmt.Println("Hello")
}

// break
for {
    if n > 0 {
        break
    }
}

// continue
for {
    if n > 0 {
        continue
    }
}

// for-range loop with keys and values (indexes on arrays)
// the values are actual copies
// favor using for-range loops
evenVals := []int{2, 4, 6, 8, 10, 12}
for i, v := range evenVals {
    fmt.Println(i, v)
}

// or if one wants only the keys
uniqueNames := map[string]bool{"Fred": true, "Raul": true, "Wilma": true}
for k := range uniqueNames {
    fmt.Println(k)
}

// flow control with switch statements
// You can switch on any type that can be compared with ==
switch size := len(word); size {
case 1, 2, 3, 4:
    fmt.Println(word, "is a short word!")
case 5:
    wordLen := len(word)
    fmt.Println(word, "is exactly the right length:", wordLen)
case 6, 7, 8, 9:
default:
    fmt.Println(word, "is a long word!")
}

// blank switches allow you to use any boolean comparison
// Favor blank switch statements over if/else chains when you have multiple related cases. 
switch wordLen := len(word); {
case wordLen < 5:
    fmt.Println(word, "is a short word!")
case wordLen > 10:
    fmt.Println(word, "is a long word!")
default:
    fmt.Println(word, "is exactly the right length.")
}

Functions

Functions are defined with func.

func SayHello() {
    fmt.PrintLn("Hello")
}

Go supports variadic parameters. It must be the last parameter in the input parameter list, and is indicated by three dots .... The variable that’s created within the function is a slice of the specified type. You use it just like any other slice.

func addTo(base int, vals ...int) []int {
    out := make([]int, 0, len(vals))
    for _, v := range vals {
        out = append(out, base+v)
    }
    return out
}

Go allows for multiple return values:

func divAndRemainder(numerator int, denominator int) (int, int, error) {
    if denominator == 0 {
        return 0, 0, errors.New("cannot divide by zero")
    }
    return numerator / denominator, numerator % denominator, nil
}

Go also allows you to specify names for your return values. What it does is pre-declaring variables that you use within the function to hold the return values.

If your function returns values, never use a blank return. It can make it very confusing to figure out what value is actually returned.

func divAndRemainder(numerator int, denominator int) (result int, remainder int, err error) {
    if denominator == 0 {
        err = errors.New("cannot divide by zero")
        return result, remainder, err
    }
    result, remainder = numerator/denominator, numerator%denominator
    return result, remainder, err
}

You can also declare anonymous functions. You don’t have to assign them to a variable, and can also call them immediately.

func main() {
    for i := 0; i < 5; i++ {
        func(j int) {
            fmt.Println("printing", j, "from inside of an anonymous function")
        }(i)
    }
}

Functions declared inside of functions are special; they are closures.

sort.Slice(people, func(i int, j int) bool {
    return people[i].Age < people[j].Age
})

Functions can also return functions:

func makeMult(base int) func(int) int {
    return func(factor int) int {
        return base * factor
    }
}

func main() {
    twoBase := makeMult(2)
    threeBase := makeMult(3)
    for i := 0; i < 3; i++ {
        fmt.Println(twoBase(i), threeBase(i))
    }
}

defer can be used to cleanup things when a function exits. defers run in a LIFO order.

func main() {
    if len(os.Args) < 2 {
        log.Fatal("no file specified")
    }
    f, err := os.Open(os.Args[1])
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close() # will be called when the function exits
    data := make([]byte, 2048)
    for {
        count, err := f.Read(data)
        os.Stdout.Write(data[:count])
        if err != nil {
            if err != io.EOF {
                log.Fatal(err)
            }
            break
        }
    }
}

Go is a call by value language - when you supply a variable for a parameter to a function, Go always makes a copy of the value of the variable.

Pointers

A pointer is simply a variable whose contents are the address where another variable is stored.

var x int32 = 10
var y bool = true
pointerX := &x
pointerY := &y
var pointerZ *string

& is the address operator. It precedes a value type and returns the address of the memory location where the value is stored * is the indirection operator. It precedes a variable of pointer type and returns the pointed-to value. This is called dereferencing. This only works with non-nil pointers. *int denotes a pointer of type int.

Pointers indicate mutable values, butyou should be careful when using them in Go - they make it harder to understand data flow and can create extra work for the garbage collector. The only time you should use pointer parameters to modify a variable is when the function expects an interface. Also, when returning values from a function, you should favor value types.

Types, Methods, and Interfaces

Defining new types in Go:

type Person struct {
    FirstName string
    LastName string
    Age int
}
type Score int
type HighScore Score
type Converter func(string)Score
type TeamScores map[string]Score

The methods for a type are defined at the package block level:

func (p Person) String() string {
    return fmt.Sprintf("%s %s, age %d", p.FirstName, p.LastName, p.Age)
}

p := Person {
    FirstName: "Fred",
    LastName:"Fredson",
    Age: 52,
}
output := p.String()

Method declarations look just like function declarations, with one addition: the receiver specification. The receiver appears between the keyword func and the name of the method. By convention, the receiver name is a short abbreviation of the type’s name, usually its first letter.

You can use both pointer receivers (the type is a pointer) or value receivers (the type is a value type). When a type has any pointer receiver methods, a common practice is to be consistent and use pointer receivers for all methods.

You can also create a function from the type itself. This is called a method expression:

f := Person.String
f(p)

Go also allows us to embed types to allow code reuse via composition and promotion.

type Employee struct {
    Name         string
    ID           string
}

func (e Employee) Description() string {
    return fmt.Sprintf("%s (%s)", e.Name, e.ID)
}

type Manager struct {
    Employee
    Reports []Employee
}

m := Manager{
    Employee: Employee{
        Name:         "Bob Bobson",
        ID:             "12345",
    },
    Reports: []Employee{},
}

fmt.Println(m.ID)            // prints 12345
fmt.Println(m.Description()) // prints Bob Bobson (12345)

Note that Manager contains a field of type Employee, but no name is assigned to that field. This makes Employee an embedded field. Any fields or methods declared on an embedded field are promoted to the containing struct and can be invoked directly on it.

Interfaces are usually named with “er” endings. To declare an interface:

type Stringer interface {
    String() string
}

Interfaces are type-safe duck typing. They are implemented implicitly - a concrete type does not declare that it implements an interface. If the method set for a concrete type contains all of the methods in the method set for an interface, the concrete type implements the interface.

You can also embed an interface in an interface:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

type ReadCloser interface {
    Reader
    Closer
}

Accept interfaces, return structs: business logic invoked by your functions should be invoked via interfaces, but the output of your functions should be a concrete type.

You can also declare a variable that accepts any value. This is very useful for reading JSON.

var i interface{}

A type assertion t, ok := i.(T) provides access to an interface value’s underlying concrete value.

type MyInt int
var i interface{}
var mine MyInt = 20
i = mine
// i2 now equals 20 (int)
i2 := i.(MyInt)
fmt.Println(i2 + 1)

When an interface could be one of multiple possible types, use a type switch instead:

func doThings(i interface{}) {
    switch j := i.(type) {
    case nil:
        // i is nil, type of j is interface{}
    case int:
        // j is of type int
    default:
        // no idea what i is, so j is of type interface{}
    }
}

In any case, you should use these techniques infrequently. One common use of a type assertion is to see if the concrete type behind the interface also implements another interface. This allows you to specify optional interfaces.

Errors

Go handles errors by returning a value of type error as the last return value for a function. This is entirely by convention. When a function executes as expected, nil is returned for the error parameter.

A new error is created from a string by calling the New function in the errors package. In most cases, you should set the other return values to their zero values when a non-nil error is returned.

func calcRemainderAndMod(numerator, denominator int) (int, int, error) {
    if denominator == 0 {
        return 0, 0, errors.New("denominator is 0")
    }
    return numerator / denominator, numerator % denominator, nil
}

Go doesn’t have special constructs to detect if an error was returned. Whenever a function returns, use an if statement to check the error variable to see if it is non-nil:

func main() {
    remainder, mod, err := calcRemainderAndMod(20, 3)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    fmt.Println(remainder, mod)
}

fmt.Errorf allows you to create a formatted error.

func doubleEven(i int) (int, error) {
    if i % 2 != 0 {
        return 0, fmt.Errorf("%d isn't an even number", i)
    }
    return i * 2, nil
}

When an error is passed back through your code, you often want to add additional context to it. When you preserve an error while adding additional information, it is called wrapping the error. When you have a series of wrapped errors, it is called an error chain.

The fmt.Errorf function has a special verb, %w. Use this to create an error whose formatted string includes the formatted string of another error and which contains the original error as well. The convention is to write %w at the end of the error format string and make the error to be wrapped the last parameter passed to fmt.Errorf.

The standard library also provides a function for unwrapping errors, the errors.Unwrap function.

If you want to wrap an error with your custom error type, your error type needs to implement the method Unwrap.

type StatusErr struct {
    Status Status
    Message string
    err error
}

func (se StatusErr) Error() string {
    return se.Message
}

func (se StatusError) Unwrap() error {
    return se.err
}

To check if the returned error or any errors that it wraps match a specific sentinel error instance, use errors.Is. By default, errors.Is uses == to compare each wrapped error with the specified error.

func main() {
    err := fileChecker("not_here.txt")
    if err != nil {
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("That file doesn't exist")
        }
    }
}

The errors.As function allows you to check if a returned error (or any error it wraps) matches a specific type. If the function returns true, an error in the error chain was found that matched, and that matching error is assigned to the second parameter.

err := AFunctionThatReturnsAnError()
var myErr MyErr
if errors.As(err, &myErr) {
    fmt.Println(myErr.Code)
}

Sometimes you find yourself wrapping multiple errors with the same message. We can simplify this code by using defer:

func DoSomeThings(val1 int, val2 string) (_ string, err error) {
    defer func() {
        if err != nil {
            err = fmt.Errorf("in DoSomeThings: %w", err)
        }
    }()
    val3, err := doThing1(val1)
    if err != nil {
        return "", err
    }
    val4, err := doThing2(val2)
    if err != nil {
        return "", err
    }
    return doThing3(val3, val4)
}

Go generates a panic whenever there is a situation where the Go runtime is unable to figure out what should happen next. As soon as a panic happens, the current function exits immediately and any defers attached to the current function start running. When those defers complete, the defers attached to the calling function run, and so on, until main is reached. The program then exits with a message and a stack trace.

Go provides a way to capture a panic to provide a more graceful shutdown or to prevent shutdown at all. The built-in recover function is called from within a defer to check if a panic happened. If there was a panic, the value assigned to the panic is returned. Once a recover happens, execution continues normally. You must call recover from within a defer because once a panic happens, only deferred functions are run.

func div60(i int) {
    defer func() {
        if v := recover(); v != nil {
            fmt.Println(v)
        }
    }()
    fmt.Println(60 / i)
}

func main() {
    for _, val := range []int{1, 2, 0, 6} {
        div60(val)
    }
}

Modules, Packages, and Imports

Library management in Go is based around three concepts:

  • Repositories is where the source code of a project is stored.
  • Modules are the root of a Go library or application, stored in a repository, and consist of one or more packages. It is not encouraged to have more than one module in a repository.
  • Packages give a module organization and structure.

Before we can use code from packages outside of the standard library, we need to make sure that we have declared that our project is a module. A collection of Go source code becomes a module when there’s a valid go.mod file in its root directory.

go mod init MODULE_PATH creates the go.mod file that makes the current directory the root of a module. The MODULE_PATH is the globally unique name that identifies your module, and is case-sensitive.

module github.com/learning-go-book/money

go 1.15

require (
    github.com/learning-go-book/formatter v0.0.0-20200921021027-5abc380940ae
    github.com/shopspring/decimal v1.2.0
)

Go’s import statement allows you to access exported constants, variables, functions, and types in another package. Go uses capitalization to determine if a package-level identifier is visible outside of the package where it is declared. An identifier whose name starts with an uppercase letter is exported.

To create a package, simply add package to the beginning of a file:

package math

func Double(a int) int {
    return a * 2
}

As a general rule, you should make the name of the package match the name of the directory that contains the package.

To override a package’s name, simply do import crand "math/rand", where crand is an alias.

To add package comments:

  • Place the comment directly before the item being documented with no blank lines between the comment and the declaration of the item.
  • Start the comment with two forward slashes (//) followed by the name of the item.
  • Use a blank comment to break your comment into multiple paragraphs.
  • Insert preformatted comments by indenting the lines.

To share a function, type, or constant between packages in your module, that you do not want to make it part of your API, use the special internal package name. The exported identifiers in that package and its subpackages are only accessible to the direct parent package of internal and the sibling packages of internal.

To reorganize an API and avoid backward-breaking changes, you can use type alias: type Bar = Foo

When you import a third-party package, you specify the location in the source code repository where the package is located.

import (
    "fmt"
    "log"
    
    "github.com/shopspring/decimal"
)

go get github.com/learning-go-book/[email protected] adds a module to go.mod using the version v1.0.0.

To handle incompatibility, Go modules follow the semantic import versioning rule. There are two parts to this rule:

  • The major version of the module must be incremented.
  • For all major versions besides 0 and 1, the path to the module must end in vN, where N is the major version (e.g. github.com/learning-go-book/simpletax/v2).

go mod tidy removes unused versions from go.sum.

Concurrency

Go’s main concurrency model is based on CSP (Communicating Sequential Processes). The goroutine is the core concept in Go’s concurrency model.

A process is an instance of a program that’s being run by a computer’s operating system. A process is composed of one or more threads. A thread is a unit of execution that is given some time to run by the operating system.

Goroutines are lightweight processes managed by the Go runtime.

A goroutine is launched by placing the go keyword before a function invocation. Just like any other function, you can pass it parameters to initialize its state. However, any values returned by the function are ignored.

func runThingConcurrently(in <-chan int, out chan<- int) {
    go func() {
        for val := range in {
            result := process(val)
            out <- result
        }
    }()
}

Goroutines communicate using channels. Like slices and maps, channels are a built-in type created using the make function:

ch := make(chan int)

Like maps, channels are reference types. When you pass a channel to a function, you are really passing a pointer to the channel. Also like maps and slices, the zero value for a channel is nil.

Use the <- operator to interact with a channel. You read from a channel by placing the <- operator to the left of the channel variable, and you write to a channel by placing it to the right:

a := <-ch // reads a value from ch and assigns it to a
ch <- b   // write the value of b to ch
v, ok := <-ch // read the value and assign it to v, ok tells whether or not the channel is open

Each value written to a channel can only be read once.

By default channels are unbuffered. Every write or read from an open, unbuffered channel causes the goroutine to pause. Go also has buffered channels. These channels buffer a limited number of writes without blocking. A buffered channel is created by specifying the capacity of the buffer when creating the channel: ch := make(chan int, 10)

The built-in functions len and cap return information about a buffered channel. You can also read from a channel using a for-range loop

When you are done writing to a channel, you close it using the built-in close function close(ch). The standard pattern is to make the writing goroutine responsible for closing the channel when there’s nothing left to write.

The select keyword allows a goroutine to read from or write to one of a set of multiple channels. Since select is responsible for communicating over a number of channels, it is often embedded within a for loop

select {
case v := <-ch:
    fmt.Println(v)
case v := <-ch2:
    fmt.Println(v)
case ch3 <- x:
    fmt.Println("wrote", x)
default:
    fmt.Println("nothing here")
}

Practices and Patterns

  • Concurrency is an implementation detail, and good API design should hide implementation details as much as possible.
  • Any time your goroutine uses a variable whose value might change, pass the current value of the variable into the goroutine.
  • Whenever you launch a goroutine function, you must make sure that it will eventually exit. Otherwise you may cause a goroutine leak.
  • The done channel pattern provides a way to signal a goroutine that it’s time to stop processing. It uses a channel to signal that it’s time to exit.
func searchData(s string, searchers []func(string) []string) []string {
    done := make(chan struct{})
    result := make(chan []string)
    for _, searcher := range searchers {
        go func(searcher func(string) []string) {
            select {
            case result <- searcher(s):
            case <-done:
            }
        }(searcher)
    }
    r := <-result
    close(done)
    return r
}
  • One can also use a cancel function to terminate a Goroutine. This assures all goroutines are closed and no leaks happen:
func countTo(max int) (<-chan int, func()) {
    ch := make(chan int)
    done := make(chan struct{})
    cancel := func() {
        close(done)
    }
    go func() {
        for i := 0; i < max; i++ {
            select {
            case <-done:
                return
            default:
                ch <- i
            }
        }
        close(ch)
    }()
    return ch, cancel
}

func main() {
    ch, cancel := countTo(10)
    for i := range ch {
        if i > 5 {
            break
        }
        fmt.Println(i)
    }
    cancel()
}
  • Buffered channels are useful when you know how many goroutines you have launched, want to limit the number of goroutines you will launch, or want to limit the amount of work that is queued up.

  • Backpressure: systems perform better overall when their components limit the amount of work they are willing to perform. We can use a buffered channel and a select statement to limit the number of simultaneous requests in a system.

  • To time out code, we can take advantage of time.After function:

func timeLimit() (int, error) {
    var result int
    var err error
    done := make(chan struct{})
    go func() {
        result, err = doSomeWork()
        close(done)
    }()
    select {
    case <-done:
        return result, err
    case <-time.After(2 * time.Second):
        return 0, errors.New("work timed out")
    }
}
  • When one goroutine needs to wait for multiple goroutines to complete their work, you need to use a WaitGroup, which is found in the sync package in the standard library.
func main() {
    var wg sync.WaitGroup
    wg.Add(3)
    go func() {
        defer wg.Done()
        doThing1()
    }()
    go func() {
        defer wg.Done()
        doThing2()
    }()
    go func() {
        defer wg.Done()
        doThing3()
    }()
    wg.Wait()
}
  • Use WaitGroups only when you have something to clean up (like closing a channel they all write to) after all of your worker goroutines exit.
  • Use sync.Once to run something only once. This is very useful for lazy-loading with init.

When to use mutexes instead of channels

A mutex is short for mutual exclusion, and the job of a mutex is to limit the concurrent execution of some code or access to a shared piece of data. This protected part is called the critical section.

The main problem with mutexes is that they obscure the flow of data through a program. When a value is passed from goroutine to goroutine over a series of channels, the data flow is clear. Access to the value is localized to a single goroutine at a time. When a mutex is used to protect a value, there is nothing to indicate which goroutine currently has ownership of the value, because access to the value is shared by all of the concurrent processes

There is a saying in the Go community to describe this philosophy: “Share memory by communicating; do not communicate by sharing memory.”

There are two mutex implementations in the standard library, both in the sync package: Mutex and RWMutex.

The Standard Library

Input/Output

Go’s input/output is backed by the io package. io.Reader and io.Writer are widely used interfaces in Go.

func countLetters(r io.Reader) (map[string]int, error) {
    buf := make([]byte, 2048)
    out := map[string]int{}
    for {
		// r.Read will return the number of bytes written into buf
        n, err := r.Read(buf)
        for _, b := range buf[:n] {
            if (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') {
                out[string(b)]++
            }
        }
		// not a real error, but indicates EOF
        if err == io.EOF {
            return out, nil
        }
		// something went wrong
        if err != nil {
            return nil, err
        }
    }
}

Examples of io.Reader implementations: gzip.NewReader(r) and string.NewReader(s).

To copy from io.Reader to io.Writer, one can use io.Copy. io.Close can be used to clean up resources.

Time

Time support is found in the time package. Two main types are used to represent time time.Duration (a period of time, based on int64) and time.Time.

The constants time.Minute, time.Hour and so on can be used to represent time.Duration. d := 2 * time.Hour + 30 * time.Minute. time.ParseDuration can be used to parse a string into a time.Duration.

time.Now returns the current time as time.Time. time.Parse converts from a string into time.Time, while time.Format formats it into a string.

Methods such as time.Day, time.Weekday can be used to extract portions of a time. Times can be compared with time.After, time.Equal and time.Before. time.Sub, time.Add and time.AddDate allow you to add or subtract from a time.

JSON

encoding/json contains methods to marshall/unmarshall JSON data into Go data types.

Given the following JSON data:

{
    "id":"12345",
    "date_ordered":"2020-05-01T13:01:02Z",
    "customer_id":"3",
    "items":[{"id":"xyz123","name":"Thing 1"},{"id":"abc789","name":"Thing 2"}]
}

We can define the following types to unmarshall it:

type Order struct {
    ID            string    `json:"id"`
    DateOrdered   time.Time `json:"date_ordered"`
    CustomerID    string    `json:"customer_id"`
    Items         []Item    `json:"items"`
    IsTest        bool      `json:"test,omitempty""`
}

type Item struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

We specify the rules for processing our JSON with struct tags, strings that are written after the fields in a struct. Even though struct tags are strings marked with backticks, they cannot extend past a single line.

Struct tags are composed of one or more tag/value pairs, written as tagName:"tagValue" and separated by spaces. Because they are just strings, the compiler cannot validate that they are formatted correctly, but go vet does. Also, note that all of these fields are exported.

When unmarshaling from JSON into a struct field with no json tag, the name match is case-insensitive. When marshaling a struct field with no json tag back to JSON, the JSON field will always have an uppercase first letter, because the field is exported.

If a field should be ignored when marshaling or unmarshaling, use a dash (-) for the name. If the field should be left out of the output when it is empty, add ,omitempty after the name.

To unmarshall the data:

var o Order
err := json.Unmarshal([]byte(data), &o)
if err != nil {
    return err
}

To marshall the data:

out, err := json.Marshal(o)

json.Decoder and json.Encoder can be used with streams of data (or anything implementing io.Reader or io.Writer, respectively).

To implement custom JSON parsing, simply implement the json.Marshaler and json.Unmarshaler interfaces.

type RFC822ZTime struct {
    time.Time
}

func (rt RFC822ZTime) MarshalJSON() ([]byte, error) {
    out := rt.Time.Format(time.RFC822Z)
    return []byte(`"` + out + `"`), nil
}

func (rt *RFC822ZTime) UnmarshalJSON(b []byte) error {
    if string(b) == "null" {
        return nil
    }
    t, err := time.Parse(`"`+time.RFC822Z+`"`, string(b))
    if err != nil {
        return err
    }
    *rt = RFC822ZTime{t}
    return nil
}

HTTP

net/http defines a Client type to make HTTP requests and receive HTTP responses. You only need to create a single http.Client for your entire program, as it properly handles multiple simultaneous requests across goroutines:

client := &http.Client{
    Timeout: 30 * time.Second,
}

Doing a request with the client:

req, err := http.NewRequestWithContext(context.Background(),
    http.MethodGet, "https://jsonplaceholder.typicode.com/todos/1", nil)
if err != nil {
    panic(err)
}

req.Header.Add("X-My-Client", "Learning Go")
res, err := client.Do(req)
if err != nil {
    panic(err)
}

// Never forget to close the body
defer res.Body.Close()

if res.StatusCode != http.StatusOK {
    panic(fmt.Sprintf("unexpected status: got %v", res.Status))
}
fmt.Println(res.Header.Get("Content-Type"))

var data struct {
    UserID    int    `json:"userId"`
    ID        int    `json:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

err = json.NewDecoder(res.Body).Decode(&data)
if err != nil {
    panic(err)
}

fmt.Printf("%+v\n", data)

http.Server can be used to serve and process requests.

The Context

A context is simply an instance that meets the Context interface defined in the context package. By convention the context is explicitly passed through your program as the first parameter of a function. The usual name for the context parameter is ctx:

func logic(ctx context.Context, info string) (string, error) {
    // do some interesting stuff here
    return "", nil
}

When you don’t have an existing context, such as at the entry point to a command-line program, create an empty initial context with the function context.Background.

ctx := context.Background()
result, err := logic(ctx, "a string")

context.TODO creates an empty context.Context that is intended for temporary use during development.

Cancellation

When running parallel code, context.WithCancel allows the creation of a context that can be used to signal that something wrong and the ongoing operations can be cancelled.

context.WithCancel wraps the old context in the returned context and also returns a function context.CancelFunc that cancels the context.

Any time you create a context that has an associated cancel function, you must call that cancel function when you are done processing, whether your processing ends in an error. This can be done with defer cancel().

Timers

Timers can be used to limit how long a program runs.

context.WithTimeout takes an existing context and time.Duration that specifies the duration until the context automatically cancels.

context.WithDeadline takes in an existing context and a time.Time that specifies the time when the context is automatically canceled.

To find out when a context will automatically cancel, use the Deadline method on context.Context. It returns a time.Time that indicates the time and a bool that indicates if there was a timeout set.

To limit operations within contexts, one can create child contexts with timeouts.

Handling Context Cancellation

The context.Context type has two methods that are used when managing cancellation.

The Done method returns a channel of struct{}. The channel is closed when the context is canceled due to a timer or the cancel function being invoked.

The Err method returns nil if the context is still active, or it returns one of two sentinel errors if the context has been canceled: context.Canceled and context.DeadlineExceeded.

Values

The context also provides a way to pass per-request metadata through your program. E.g. passing the request/response to an HTTP request handler and its associated middleware.

context.WithValue(ctx, key, value) lets you put values in a context. To get a value from a context, use context.Value.

package main

// Pattern to guarantee key uniqueness.
// No other package can use the type/constant, therefore
// there is no risk of collisions.
type userKey int
const key userKey = 1

// API code, may be private if no one needs eat outside
// of this package
func ContextWithUser(ctx context.Context, user string) context.Context {
    return context.WithValue(ctx, key, user)
}

func UserFromContext(ctx context.Context) (string, bool) {
    user, ok := ctx.Value(key).(string)
    return user, ok
}

func Middleware(h http.Handler) http.Handler {
    return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
        user, err := extractUser(req)
        if err != nil {
            rw.WriteHeader(http.StatusUnauthorized)
            return
        }
		
        ctx := req.Context()
        ctx = ContextWithUser(ctx, user)
        req = req.WithContext(ctx)
        h.ServeHTTP(rw, req)
    })
}

Writing Tests

The testing package provides types and funtions to write tests, while go test runs them and generates reports.

The code:

// adder.go
func addNumbers(x, y int) int {
    return x + y
}

The test:

// adder_test.go
func Test_addNumbers(t *testing.T) {
    result := addNumbers(2,3)
    if result != 5 {
        t.Error("incorrect result: expected 5, got", result)
    }
}

Every test file name must end with _test.go. Test functions must be named starting with Test. The simplest way to report an error is checking a value and triggering t.Error(msg). The testing package also has Errorf (like Printf), and Fatal/Fatalf, which stop the execution of the tested function.

TestMain is a special function that is run once before the test suite (setup). t.Cleanup is used to cleanup temporary resources created for a test (an alternate to defer).

To store test data, use a testdata directory on each package you need. This can be accessed with a relative path.

To test your public API, you create a packagename_test. You would then import your code like any other:

package adder_test

import (
    "testing"
    "adder"
)

func TestAddNumbers(t *testing.T) {
    result := adder.AddNumbers(2, 3)
    if result != 5 {
        t.Error("incorrect result: expected 5, got", result)
    }
}

The go-cmp library can be used to compare things and report their differences:

import "github.com/google/go-cmp/cmp"

func TestCreatePerson(t *testing.T) {
    expected := Person{
        Name: "Dennis",
        Age:  37,
    }
    
	result := CreatePerson("Dennis", 37)
    
	if diff := cmp.Diff(expected, result); diff != "" {
        t.Error(diff)
    }
}

To control which data is compared (e.g. ignore a timestamp), use a custom function:

comparer := cmp.Comparer(func(x, y Person) bool {
    return x.Name == y.Name && x.Age == y.Age
})

if diff := cmp.Diff(expected, result, comparer); diff != "" {
    t.Error(diff)
}

Table tests allow you to create a collection of input/outputs to be tested (like PHPUnit providers). There are libraries that can make this easier to use:

data := []struct {
    name     string
    num1     int
    num2     int
    op       string
    expected int
    errMsg   string
}{
    {"addition", 2, 2, "+", 4, ""},
    {"subtraction", 2, 2, "-", 0, ""},
    {"multiplication", 2, 2, "*", 4, ""},
    {"division", 2, 2, "/", 1, ""},
    {"bad_division", 2, 0, "/", 0, `division by zero`},
}

for _, d := range data {
    t.Run(d.name, func(t *testing.T) {
        // ... test code comes here
    })
}

The go test -cover produces test-coverage reports, and go tool cover can generate an HTML report from it.

Benchmarks

The testing library also includes support for benchmarking. In Go, benchmarks are functions that start with Benchmark. *testing.B includes all functionality of *testing.T plus benchmarking.

var blackhole int

func BenchmarkFileLen1(b *testing.B) {
    for i := 0; i < b.N; i++ {
        result, err := FileLen("testdata/data.txt", 1)
        if err != nil {
            b.Fatal(err)
        }
        blackhole = result
    }
}

To run a benchmark, call go test -bench.

Finding Concurrency Problems

go test -race helps you find data race conditions. Running it slows down a binary more than 10 times, therefore is not wise to always use it.

Reflection, unsafe and cgo

Avoid using unsafe unless you know what you are doing, and you need the performance improvements that it provides.

cgo is used to integrate with C libraries.

Reflection

Reflection is available on the reflect package. Reflection is implemented around three core concepts: types, kinds, and values.

A type in reflection is… a type. reflect.TypeOf(a) will return the reflect.Type of a with methods such as:

  • Name (returns the type name),
  • Kind returns reflect.Kind, which is a constant that says what the type is made of - a slice, a map, a pointer, a struct, an interface, a string, an array, a function, an int, or some other primitive type.
  • Elem: types in Go have references to other types and Elem is how to find out what the contained type is.
  • NumField returns the number of fields a struct has.
  • Field lets you reflect struct fields by index, returning a reflect.StructField. You can access struct tags with field.Tag.Get("tagname").

To access the value of a variable via reflection, use reflect.ValueOf(v), which returns a reflect.Value and has methods such as:

  • Elem returns the value of the reflected variable.
  • To set the values, use methods such as SetInt(1), SetBool(true), etc.
  • isValid returns true if reflect.Value holds anything other than a nil interface.
  • isNil returns true if reflect.Value is nil, and the reflect.Kind is nullable.