Foramtting

With Go we take an unusual approach and let the machine take care of most formatting issues. The gofmt program (also available as go fmt, which operates at the package level rather than source file level) reads a Go program and emits the source in a standard style of indentation and vertical alignment, retaining and if necessary reformatting comments.

$ cat formatting.go && gofmt formatting.go
package formatting
type T struct {

        name string // name of the object
            value int // its value
        }
package formatting

type T struct {
    name  string // name of the object
    value int    // its value
}

Commentary

Go provides C-style /* */ block comments and C++-style // line comments. Line comments are the norm; block comments appear mostly as package comments, but are useful within an expression or to disable large swaths of code.

The program—​and web server--godoc processes Go source files to extract documentation about the contents of the package. Comments that appear before top-level declarations, with no intervening newlines, are extracted along with the declaration to serve as explanatory text for the item. The nature and style of these comments determines the quality of the documentation godoc produces.

/*
Package regexp implements a simple library for regular expressions.

The syntax of the regular expressions accepted is:

    regexp:
        concatenation { '|' concatenation }
    concatenation:
        { closure }
    closure:
        term [ '*' | '+' | '?' ]
    term:
        '^'
        '$'
        '.'
        character
        '[' [ '^' ] character-ranges ']'
        '(' regexp ')'
*/
package regexp
$ go doc builtin new
package builtin // import "builtin"

func new(Type) *Type
    The new built-in function allocates memory. The first argument is a type,
    not a value, and the value returned is a pointer to a newly allocated zero
    value of that type.
$ go doc sync Mutex
package sync // import "sync"

type Mutex struct {
    // Has unexported fields.
}
    A Mutex is a mutual exclusion lock. The zero value for a Mutex is an
    unlocked mutex.

    A Mutex must not be copied after first use.

func (m *Mutex) Lock()
func (m *Mutex) Unlock()

Names

The visibility of a name outside a package is determined by whether its first character is upper case.

Package names

  • By convention, packages are given lower case, single-word names; there should be no need for underscores or mixedCaps.

  • Another convention is that the package name is the base name of its source directory; the package in src/encoding/base64 is imported as "encoding/base64" but has name base64, not encoding_base64 and not encodingBase64.

  • Use the package structure to help you choose good names.

    • The importer of a package will use the name to refer to its contents, so exported names in the package can use that fact to avoid stutter.

    • For instance, the buffered reader type in the bufio package is called Reader, not BufReader, because users see it as bufio.Reader, which is a clear, concise name.

    • Moreover, because imported entities are always addressed with their package name, bufio.Reader does not conflict with io.Reader.

    • Similarly, the function to make new instances of ring.Ring—which is the definition of a constructor in Go—would normally be called NewRing, but since Ring is the only type exported by the package, and since the package is called ring, it’s called just New, which clients of the package see as ring.New.

Getters

  • Go doesn’t provide automatic support for getters and setters.

  • There’s nothing wrong with providing getters and setters yourself, and it’s often appropriate to do so, but it’s neither idiomatic nor necessary to put Get into the getter’s name.

  • If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner.

  • A setter function, if needed, will likely be called SetOwner.

  • Both names read well in practice:

      owner := obj.Owner()
      if owner != user {
          obj.SetOwner(user)
      }

Interface names

  • By convention, one-method interfaces are named by the method name plus an -er suffix or similar modification to construct an agent noun: Reader, Writer, Formatter, CloseNotifier etc.

  • There are a number of such names and it’s productive to honor them and the function names they capture.

  • Read, Write, Close, Flush, String and so on have canonical signatures and meanings.

  • To avoid confusion, don’t give your method one of those names unless it has the same signature and meaning.

  • Conversely, if your type implements a method with the same meaning as a method on a well-known type, give it the same name and signature; call your string-converter method String not ToString.

MixedCaps

Finally, the convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword names.

Semicolons

  • Like C, Go’s formal grammar uses semicolons to terminate statements, but unlike in C, those semicolons do not appear in the source.

    If the newline comes after a token that could end a statement, insert a semicolon.

  • Idiomatic Go programs have semicolons only in places such as for loop clauses, to separate the initializer, condition, and continuation elements.

  • They are also necessary to separate multiple statements on a line, should you write code that way.

Control structures

  • There is no do or while loop, only a slightly generalized for; switch is more flexible;

  • if and switch accept an optional initialization statement like that of for;

  • break and continue statements take an optional label to identify what to break or continue;

  • and there are new control structures including a type switch and a multiway communications multiplexer, select.

  • There are no parentheses and the bodies must always be brace-delimited.

If

if x > 0 {
    return y
}
if f, err: = os.Open(name); err != nil {
   return err
}

For

// Like a C for
for init; condition; post { }

// Like a C while
for condition { }

// Like a C for(;;)
for { }

// Like a C do-while
for {
    // do something
    if condition; {
        break
    }
}

If you’re looping over an array, slice, string, or map, or reading from a channel, a range clause can manage the loop.

for key, value := range map {
}

// If you only need the second item in the range (the value),
// use the blank identifier, an underscore, to discard the first:
for _, value := range map {
}

for index, value := range array {
}

for value := range channel {
}

For strings, the range does more work for you, breaking out individual Unicode code points by parsing the UTF-8. Erroneous encodings consume one byte and produce the replacement rune U+FFFD. (The name (with associated builtin type) rune is Go terminology for a single Unicode code point.)

for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
    fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
// Output:
// character U+65E5 '日' starts at byte position 0
// character U+672C '本' starts at byte position 3
// character U+FFFD '�' starts at byte position 6
// character U+8A9E '語' starts at byte position 7

Go has no comma operator and ++ and -- are statements not expressions. Thus if you want to run multiple variables in a for you should use parallel assignment (although that precludes ++ and --).

// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
    a[i], a[j] = a[j], a[i]
}

Switch

Go’s switch is more general than C’s.

  • The expressions need not be constants or even integers,

  • the cases are evaluated top to bottom until a match is found,

  • and if the switch has no expression it switches on true.

  • It’s therefore possible—​and idiomatic—​to write an if-else-if-else chain as a switch.

  • There is no automatic fall through, but cases can be presented in comma-separated lists.

  • Although they are not nearly as common in Go as some other C-like languages, break statements can be used to terminate a switch early.

  • Sometimes, though, it’s necessary to break out of a surrounding loop, not the switch, and in Go that can be accomplished by putting a label on the loop and "breaking" to that label.

  • Of course, the continue statement also accepts an optional label but it applies only to loops.

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)
}
Loop:
    for n := 0; n < len(src); n += size {
        switch {
        case src[n] < sizeOne:
            if validateOnly {
                break
            }
            size = 1
            update(src[n])

        case src[n] < sizeTwo:
            if n+1 >= len(src) {
                err = errShortInput
                break Loop
            }
            if validateOnly {
                break
            }
            size = 2
            update(src[n] + src[n+1]<<shift)
        }
    }

Type switch

A switch can also be used to discover the dynamic type of an interface variable.

  • Such a type switch uses the syntax of a type assertion with the keyword type inside the parentheses.

  • If the switch declares a variable in the expression, the variable will have the corresponding type in each clause.

  • It’s also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different type in each case.

var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
	fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has
case bool:
	fmt.Printf("boolean %t\n", t) // t has type bool
case int:
	fmt.Printf("integer %d\n", t) // t has type int
case *bool:
	fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
	fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}

Select

The select statement lets a goroutine wait on multiple communication operations.

A select blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.

The default case in a select is run if no other case is ready.

Use a default case to try a send or receive without blocking:

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(50 * time.Millisecond)
		}
	}
}

Break, Continue and Goto

A break statement terminates execution of the innermost for, switch, or select statement within the same function.

A continue statement begins the next iteration of the innermost for loop at its post statement within the same function.

A goto statement transfers control to the statement with the corresponding label within the same function.

func main() {
	tick := time.Tick(100 * time.Millisecond)
	boom := time.After(500 * time.Millisecond)

RawLoop:
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
			continue RawLoop // just for demo, needless
		case <-boom:
			fmt.Println("BOOM!")
			break RawLoop
		default:
			fmt.Println(".   .")
			time.Sleep(50 * time.Millisecond)
			goto RawLoop // just for demo, needless
		}
	}
}

Fallthrough

A fallthrough statement transfers control to the first statement of the next case clause in an expression switch statement. It may be used only as the final non-empty statement in such a clause.

func main() {
	switch {
	case 10 > 11:
		fmt.Println("10 > 11")
	case 1 < 5:
		fallthrough
	case 1 > 10:
		fmt.Println("1 > 10")
	}
}

// Output:
// 1 > 10

Defer

A defer statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.

Functions

Multiple return values

func (file *File) Write(b []byte) (n int, err error)

Named result parameters

  • The return or result "parameters" of a Go function can be given names and used as regular variables, just like the incoming parameters.

  • When named, they are initialized to the zero values for their types when the function begins;

  • if the function executes a return statement with no arguments, the current values of the result parameters are used as the returned values.

Defer

  • Go’s defer statement schedules a function call (the deferred function) to be run immediately before the function executing the defer returns.

  • It’s an unusual but effective way to deal with situations such as resources that must be released regardless of which path a function takes to return.

      func ReadFile(filename string) ([]byte, error) {
          f, err := os.Open(filename)
          if err != nil {
              return nil, err
          }
          defer f.Close()
          return ReadAll(f)
      }
  • The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes.

  • Deferred functions are executed in LIFO order (stacking style).

      for i := 0; i < 5; i++ {
      	defer fmt.Printf("%d ", i)
      }
    
      // Output:
      // 4 3 2 1 0
      // All function values created by this loop "capture"
      // and share the same variable—an addressable storage location,
      // not its value at that particular moment.
      for i := 0; i < 5; i++ {
          defer func() {
              fmt.Print(i, " ")
          }()
      }
    
      // Output:
      // 5 5 5 5 5
      for i := 0; i < 5; i++ {
          // declares inner i, intialized to outer i
          i := i
          defer func() {
              fmt.Print(i, " ")
          }()
      }
    
      // Output:
      // 4 3 2 1 0

Data types

// any is an alias for interface{} and is equivalent to interface{} in all ways.
any

// comparable is an interface that is implemented by all comparable types
// (booleans, numbers, strings, pointers, channels, arrays of comparable types,
// structs whose fields are all comparable types). The comparable interface may
// only be used as a type parameter constraint, not as the type of a variable.
comparable

bool // true false

string

int8  int16  int32  int64
uint8 uint16 uint32 uint64 uintptr
int uint // either 32 or 64 bits

byte // alias for uint8

rune // alias for int32
     // represents a Unicode code point

float32 float64

complex64 complex128

// more types
pointers structs array slices maps functions interfaces channels

Strings, bytes, runes and characters

  • Go source code is always UTF-8.

  • A string holds arbitrary bytes.

  • A string literal, absent byte-level escapes, always holds valid UTF-8 sequences.

  • Those sequences represent Unicode code points, called runes.

  • No guarantee is made in Go that characters in strings are normalized.

func main() {
	const nihongo = "日本語"
	for _, runeValue := range nihongo {
		fmt.Printf("%#U ", runeValue)
	}
	fmt.Println()

	for index := 0; index < len(nihongo); index++ {
		fmt.Printf("%x ", nihongo[index])
	}
	fmt.Println()

	for index := 0; index < len(nihongo); index++ {
		fmt.Printf("%q ", nihongo[index])
	}
	fmt.Println()

	for index := 0; index < len(nihongo); index++ {
		fmt.Printf("%+q ", nihongo[index])
	}
	fmt.Println()
}

// Output:
// U+65E5 '日' U+672C '本' U+8A9E '語'
// e6 97 a5 e6 9c ac e8 aa 9e
// 'æ' '\u0097' '¥' 'æ' '\u009c' '¬' 'è' 'ª' '\u009e'
// '\u00e6' '\u0097' '\u00a5' '\u00e6' '\u009c' '\u00ac' '\u00e8' '\u00aa' '\u009e'

Pointers

// A pointer holds the memory address of a value.
// Unlike C, Go has no pointer arithmetic.

// The type `*T` is a pointer to a `T` value. Its zero value is `nil`.
var p *int

i := 42
// The `&` operator generates a pointer to its operand.
p = &i

// The `*` operator ("dereferencing" or "indirecting") denotes the pointer's underlying value.
*p = 21

Structs

// A struct is a collection of fields.
type Vertex struct {
    X, Y int
}

var (
    // A struct literal denotes a newly allocated struct value by listing the values of its fields.
    v1 = Vertex{1, 2}  // has type Vertex

    // You can list just a subset of fields by using the Name: syntax.
    // (And the order of named fields is irrelevant.)
    v2 = Vertex{X: 1}  // Y:0 is implicit
    v3 = Vertex{}      // X:0 and Y:0

    // The special prefix & returns a pointer to the struct value
    p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
    // Struct fields are accessed using a dot.
    p.X = 1e9
    fmt.Println(v1, p, v2, v3)
}

Arrays

  • The type [n]T is an array of n values of type T.

  • Arrays are values.

    Assigning one array to another copies all the elements.

  • In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.

  • The size of an array is part of its type.

    The types [10]int and [20]int are distinct, so arrays cannot be resized.

var a [2]string
a[0] = "Hello"
a[1] = "World"

// an array literal
primes := [6]int{2, 3, 5, 7, 11, 13}

Slices

  • A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array.

  • The type []T is a slice with elements of type T.

  • A slice is formed by specifying two indices, a low and high bound, separated by a colon:

      // This selects a half-open range which includes the first element, but excludes the last one.
      a[low : high]
  • The following expression creates a slice which includes elements 1 through 3 of a:

      a[1:4]

Slices are like references to arrays

  • A slice does not store any data, it just describes a section of an underlying array.

  • A slice hold references to an underlying array, and if you assign one slice to another, both refer to the same 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

  • A slice literal is like an array literal without the length.

      []bool{true, true, false}

Slice defaults

  • When slicing, you may omit the high or low bounds to use their defaults instead.

  • The default is zero for the low bound and the length of the slice for the high bound.

    // For the array
    var a [10]int
    // these slice expressions are equivalent:
    a[0:10]
    a[:10]
    a[0:]
    a[:]

Slice length and capacity

  • A slice has both a length and a 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).

  • You can extend a slice’s length by re-slicing it, provided it has sufficient capacity.

Nil slices

  • The zero value of a slice is nil.

  • A nil slice has a length and capacity of 0 and has no underlying array.

Appending to a slice

  • It is common to append new elements to a slice, and so Go provides a built-in append function.

      func append(s []T, vs ...T) []T
  • The resulting value of append is a slice containing all the elements of the original slice plus the provided values.

  • If the backing array of s is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.

      var s []int
    
      // append works on nil slices.
      s = append(s, 0)
    
      // The slice grows as needed.
      s = append(s, 1)
    
      // We can add more than one element at a time.
      s = append(s, 2, 3, 4)

Maps

  • Maps are a convenient and powerful built-in data structure that associate values of one type (the key) with values of another type (the element or value).

  • The key can be of any type for which the equality operator is defined, such as integers, floating point and complex numbers, strings, pointers, interfaces (as long as the dynamic type supports equality), structs and arrays.

  • Slices cannot be used as map keys, because equality is not defined on them.

  • Like slices, maps hold references to an underlying data structure.

    If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller.

  • The zero value of a map is nil.

    A nil map has no keys, nor can keys be added.

  • Map literals are like struct literals, but the keys are required.

    var m map[string]int // <nil>
    m = map[string]int{
        "hello": 100,
        "world": 200,
    }
  • The make function returns a map of the given type with an optional capacity hint as arguments, initialized and ready for use.

    // m := make(map[string]int, 100)
    m := make(map[string]int)
    
    // insert or update an element
    m["Answer"] = 42
    
    // delete an element:
    // The delete function doesn’t return anything, and will do nothing if the specified key doesn’t exist.
    delete(m, "Answer")
    
    // retrieve an element
    // If the requested key doesn’t exist, we get the value type’s zero value.
    v := m["Answer"]
    
    // test that a key is present with a two-value assignment
    v, ok := m["Answer"]

Functions

  • Functions are values too.

    They can be passed around just like other values.

  • Function values may be used as function arguments and return values.

  • Go functions may be closures.

    • A closure is a function value that references variables from outside its body.

    • The function may access and assign to the referenced variables; in this sense the function is "bound" to the variables.

      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 < 3; i++ {
      		fmt.Println(
      			pos(i),
      			neg(-2*i),
      		)
      	}
      
      	// Output:
      	// 0 0
      	// 1 -2
      	// 3 -6
      }

Methods

  • Go does not have classes.

    However, you can define methods on any named type (except a pointer or an interface).

  • A method is a function with a special receiver argument.

    The receiver appears in its own argument list between the func keyword and the method name.

    You can only declare a method with a receiver whose type is defined in the same package as the method.

Choosing a value or pointer receiver

  • There are two reasons to use a pointer receiver.

    • The first is so that the method can modify the value that its receiver points to.

    • The second is to avoid copying the value on each method call.

      This can be more efficient if the receiver is a large struct, for example.

  • In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.

  • The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.

    This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake.

    There is a handy exception, though. When the value is addressable, the language takes care of the common case of invoking a pointer method on a value by inserting the address operator automatically.

      package bufio // import "bufio"
    
      func (b *Reader) Read(p []byte) (n int, err error)
    
      func (b *Writer) Write(p []byte) (nn int, err error)

Nil is a valid receiver value

  • Just as some functions allow nil pointers as arguments, so do some methods for their receiver, especially if nil is a meaningful zero value of the type, as with maps and slices.

  • When you define a type whose methods allow nil as a receiver value, it’s worth pointing this out explicitly in its documentation comment.

Interfaces

An interface type defines a type set. A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.

An interface type is specified by a list of interface elements. An interface element is either a method or a type element, where a type element is a union of one or more type terms. A type term is either a single type or a single underlying type.

Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here.

Interfaces are implemented implicitly

  • A type implements an interface by implementing its methods.

    There is no explicit declaration of intent, no "implements" keyword.

  • Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement.

Interface values

  • Under the hood, interface values can be thought of as a tuple of a value and a concrete type:

    (value, type)
  • An interface value holds a value of a specific underlying concrete type.

  • Calling a method on an interface value executes the method of the same name on its underlying type.

Interface values with nil underlying values

  • If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.

  • In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver.

  • Note that an interface value that holds a nil concrete value is itself non-nil.

    type I interface {
    	M()
    }
    
    type T struct{}
    
    func (t *T) M() {
    	if t == nil {
    		fmt.Println("<nil>")
    		return
    	}
    }
    
    func main() {
    	var i I
    	var t *T
    	i = t
    	i.M()
    	fmt.Printf("(%v, %T)\n", i, i)
    
    	i = &T{}
    	i.M()
    	fmt.Printf("(%v, %T)\n", i, i)
    
    	// Output:
    	// <nil>
    	// (<nil>, *main.T)
    	// (&{}, *main.T)
    }

Nil interface values

  • A nil interface value holds neither value nor concrete type.

  • Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.

    var i I
    fmt.Printf("(%v, %T)\n", i, i)
    i.M()
    // (<nil>, <nil>)
    // panic: runtime error: invalid memory address or nil pointer dereference

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. (Every type implements at least zero methods.)

  • Empty interfaces are used by code that handles values of unknown type.

  • For convenience, the predeclared type any is an alias for the empty interface.

    $ go doc builtin.any
    package builtin // import "builtin"
    
    type any = interface{}
        any is an alias for interface{} and is equivalent to interface{} in all
        ways.
    
    func recover() any

General interfaces

  • In their most general form, an interface element may also be an arbitrary type term T, or a term of the form ~T specifying the underlying type T, or a union of terms t1|t2|…|tn.

  • By construction, an interface’s type set never contains an interface type.

    // An interface representing only the type int.
    type I0 interface {
    	int
    }
    
    // An interface representing all types with underlying type int.
    type I1 interface {
    	~int
    }
    
    // An interface representing all types with underlying type int that implement the String method.
    type I2 interface {
    	~int
    	String() string
    }
    
    // An interface representing an empty type set: there is no type that is both an int and a string.
    type I3 interface {
    	int
    	string
    }
  • In a term of the form ~T, the underlying type of T must be itself, and T cannot be an interface.

    type MyInt int
    
    type MyI interface {
    	~[]byte  // the underlying type of []byte is itself
    	~MyInt   // illegal: the underlying type of MyInt is not MyInt
    	~error   // illegal: error is an interface
    }
  • Union elements denote unions of type sets:

    // The Float interface represents all floating-point types
    // (including any named types whose underlying types are
    // either float32 or float64).
    type Float interface {
    	~float32 | ~float64
    }

Generality

  • If a type exists only to implement an interface and will never have exported methods beyond that interface, there is no need to export the type itself.

  • Exporting just the interface makes it clear the value has no interesting behavior beyond what is described in the interface.

  • It also avoids the need to repeat the documentation on every instance of a common method.

  • In such cases, the constructor should return an interface value rather than the implementing type.

Interface conversions and type assertions

  • A type assertion provides access to an interface value’s underlying concrete value.

    t := i.(T)

    This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t.

    If i does not hold a T, the statement will trigger 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 := i.(T)

    If i 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.

Type switches

  • The declaration in a type switch has the same syntax as a type assertion i.(T), but the specific type T is replaced with the keyword type.

    switch v := i.(type) {
    case T:
        // here v has type T
    case S:
        // here v has type S
    default:
        // no match; here v has the same type as i
    }

Embedding: interfaces and structs

  • Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.

      package io // import "io"
    
      type Reader interface {
          Read(p []byte) (n int, err error)
      }
    
      type Writer interface {
          Write(p []byte) (n int, err error)
      }
    
      // ReadWriter is the interface that combines the Reader and Writer interfaces.
      type ReadWriter interface {
          Reader
          Writer
      }
      package bufio // import "bufio"
    
      type Reader struct {
          // Has unexported fields.
      }
    
      func (b *Reader) Read(p []byte) (n int, err error)
    
      type Writer struct {
          // Has unexported fields.
      }
    
      func (b *Writer) Write(p []byte) (nn int, err error)
    
      // ReadWriter stores pointers to a Reader and a Writer.
      // It implements io.ReadWriter.
      type ReadWriter struct {
          *Reader
          *Writer
      }
  • There’s an important way in which embedding differs from subclassing.

    • When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one.

      For example, when the Read method of a bufio.ReadWriter is invoked, the receiver is the reader field of the ReadWriter, not the ReadWriter itself.

      type Reader struct {
      }
      
      func (r *Reader) Read() {
      	fmt.Println("Read")
      }
      
      type Writer struct {
      }
      
      func (r *Writer) Write() {
      	fmt.Println("Write")
      }
      
      type ReadWriter struct {
      	*Reader
      	*Writer
      }
      
      func main() {
      	rw := ReadWriter{}
      	rw.Read() // same as rw.Reader.Read()
      	rw.Reader.Read()
      	// Output:
      	// Read
      	// Read
      }
  • Embedding types introduces the problem of name conflicts but the rules to resolve them are simple.

    • First, a field or method X hides any other item X in a more deeply nested part of the type.

    • Second, if the same name appears at the same nesting level, it is usually an error.

      However, if the duplicate name is never mentioned in the program outside the type definition, it is OK.

      This qualification provides some protection against changes made to types embedded from outside; there is no problem if a field is added that conflicts with another field in another subtype if neither field is ever used.

Channels

  • Channels are a typed conduit through which you can send and receive values with the channel operator, <-.

    ch <- v    // Send v to channel ch.
    v := <-ch  // Receive from ch, and assign value to v.
    
    // (The data flows in the direction of the arrow.)
  • Like maps and slices, channels must be created before use:

    // By default, sends and receives block until the other side is ready.
    // This allows goroutines to synchronize without explicit locks or condition variables.
    blockChan := make(chan int)
    
    // Sends to a buffered channel block only when the buffer is full.
    // Receives block when the buffer is empty.
    bufChan := make(chan int, 100)
  • A sender can close a channel to indicate that no more values will be sent.

    • After calling close, and after any previously sent values have been received, receive operations will return the zero value for the channel’s type without blocking.

    • Note that it is only necessary to close a channel if the receiver is looking for a close. Closing the channel is a control signal on the channel indicating that no more data follows.

    • The multi-valued assignment form of the receive operator reports whether a received value was sent before the channel was closed.

      // ok is false if there are no more values to receive and the channel is closed.
      v, ok := <-ch
    • The loop for v := range c receives values from the channel repeatedly until it is closed.

    • Attempting to close an already-closed channel causes a panic, as does closing a nil channel.

    • Sending to a closed channel causes a run-time panic.

    • Note: Only the sender should close a channel, never the receiver.

      Sending on a closed channel will cause a panic.

    • Another note: Channels aren’t like files; you don’t usually need to close them.

      Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.

  • A channel may be constrained only to send or only to receive by assignment or explicit conversion.

    func main() {
    	var (
    		_ = make(chan int)   // bidirectional
    		_ = make(<-chan int) // receive-only
    		_ = make(chan<- int) // send-only
    	)
    
    	ch := make(chan int)
    
    	// send-only
    	go func(ch chan<- int) {
    		for i := 0; i < 3; i++ {
    			ch <- i
    		}
    		close(ch)
    	}(ch)
    
    	// receive-only
    	go func(ch <-chan int) {
    		for v := range ch {
    			fmt.Println(v)
    		}
    	}(ch)
    
    	time.Sleep(time.Millisecond)
    	// Output:
    	// 0
    	// 1
    	// 2
    }
    func main() {
    	ch1 := make(chan int)
    	ch2 := make(chan int, 2) // buffering channel
    	quit := make(chan int)
    
    	go func() {
    		for i := 1; ; i++ {
    			ch1 <- 2 * i
    			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
    		}
    	}()
    
    	go func(ch chan<- int) {
    		for i := 1; ; i++ {
    			ch <- 2*i + 1
    			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
    		}
    	}(ch2)
    
    	go func() {
    		<-time.After(time.Duration(5e3) * time.Millisecond)
    		quit <- 0
    	}()
    
    	//  The select statement lets a goroutine wait on multiple communication operations.
    	//  A select blocks until one of its cases can run, then it executes that case.
    	//  It chooses one at random if multiple are ready.
    	ch3 := make(chan int)
    	timeout := time.After(500 * time.Millisecond)
    
    	go func() {
    		defer close(ch3)
    		for {
    			// multiplexing: ch1 + ch2 => ch3
    			select {
    			case ch3 <- <-ch1:
    			case ch3 <- <-ch2:
    			case <-timeout:
    				fmt.Println("You're too slow.")
    				return
    			case <-quit:
    				fmt.Println("Quit.")
    				return
    			}
    		}
    	}()
    
    	for v := range ch3 {
    		fmt.Println(v)
    	}
    }

Type conversions

The expression T(v) converts the value v to the type T.

// Some numeric conversions:

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

// Or, put more simply:

i := 42
f := float64(i)
u := uint(f)

Type parameters and Generics

Go functions can be written to work on multiple types using type parameters. The type parameters of a function appear between brackets, before the function’s arguments.

func Index[T comparable](s []T, x T) int

This declaration means that s is a slice of any type T that fulfills the built-in constraint comparable. x is also a value of the same type.

comparable is a useful constraint that makes it possible to use the == and != operators on values of the type.

func main() {
	si := []int{10, 20, 15, -10}
	fmt.Println(Index(si, 15))

	ss := []string{"foo", "bar", "baz"}
	fmt.Println(Index(ss, "buz"))
}
// Output:
// 2
// -1

// Index returns the index of x in s, or -1 if not found.
func Index[T comparable](s []T, x T) int {
	for i, v := range s {
		// v and x are type T, which has the comparable
		// constraint, so we can use == here.
		if x == v {
			return i
		}
	}
	return -1
}

In addition to generic functions, Go also supports generic types. A type can be parameterized with a type parameter, which could be useful for implementing generic data structures.

package main

import (
	"fmt"
	"strings"

	"golang.org/x/exp/constraints"
)

type ComparableOrdered interface {
	comparable
	constraints.Ordered
}

// List represents a singly-linked list that holds
// values of `ComparableOrdered` type.
type List[T ComparableOrdered] struct {
	next *List[T]
	val  T
}

func (head *List[T]) append(vals ...T) {
	var a = func(val T) {
		tail := head
		for tail.next != nil {
			tail = tail.next
		}
		tail.next = &List[T]{val: val}
	}
	for _, val := range vals {
		a(val)
	}
}
func (head *List[T]) max() T {
	max := head.val
	node := head.next
	for node != nil {
		if node.val > max {
			max = node.val
		}
		node = node.next
	}
	return max
}

func (head *List[T]) String() string {
	var b strings.Builder
	node := head
	for node != nil {
		fmt.Fprintf(&b, "%v", node.val)
		node = node.next
		if node != nil {
			fmt.Fprint(&b, " -> ")
		}
	}
	return b.String()
}

func main() {
	list := &List[int]{val: 20}
	list.append(10, 30, 60)
	list.append(40)
	fmt.Printf("list: %v\n", list)
	fmt.Printf("max: %v", list.max())
	// Output:
	// list: 20 -> 60 -> 30 -> 10 -> 40
	// max: 60
}

fmt.Printf

Package fmt implements formatted I/O with functions analogous to C’s printf and scanf. The format 'verbs' are derived from C’s but are simpler.

  • The Printing verbs

    General:
    %v	the value in a default format
    	when printing structs, the plus flag (%+v) adds field names
    %#v	a Go-syntax representation of the value
    %T	a Go-syntax representation of the type of the value
    %%	a literal percent sign; consumes no value
    Boolean:
    %t	the word true or false
    Integer:
    %b	base 2
    %c	the character represented by the corresponding Unicode code point
    %d	base 10
    %o	base 8
    %O	base 8 with 0o prefix
    %q	a single-quoted character literal safely escaped with Go syntax.
    %x	base 16, with lower-case letters for a-f
    %X	base 16, with upper-case letters for A-F
    %U	Unicode format: U+1234; same as "U+%04X"
    Floating-point and complex constituents:
    %b	decimalless scientific notation with exponent a power of two,
    	in the manner of strconv.FormatFloat with the 'b' format,
    	e.g. -123456p-78
    %e	scientific notation, e.g. -1.234456e+78
    %E	scientific notation, e.g. -1.234456E+78
    %f	decimal point but no exponent, e.g. 123.456
    %F	synonym for %f
    %g	%e for large exponents, %f otherwise. Precision is discussed below.
    %G	%E for large exponents, %F otherwise
    %x	hexadecimal notation (with decimal power of two exponent), e.g. -0x1.23abcp+20
    %X	upper-case hexadecimal notation, e.g. -0X1.23ABCP+20
    String and slice of bytes (treated equivalently with these verbs):
    %s	the uninterpreted bytes of the string or slice
    %q	a double-quoted string safely escaped with Go syntax
    %x	base 16, lower-case, two characters per byte
    %X	base 16, upper-case, two characters per byte
    Slice:
    %p	address of 0th element in base 16 notation, with leading 0x
    Pointer:
    %p	base 16 notation, with leading 0x
    The %b, %d, %o, %x and %X verbs also work with pointers,
    formatting the value exactly as if it were an integer.
    The default format for %v is:
    bool:                    %t
    int, int8 etc.:          %d
    uint, uint8 etc.:        %d, %#x if printed with %#v
    float32, complex64, etc: %g
    string:                  %s
    chan:                    %p
    pointer:                 %p
    For compound objects, the elements are printed using these rules, recursively, laid out like this:
    struct:             {field0 field1 ...}
    array, slice:       [elem0 elem1 ...]
    maps:               map[key1:value1 key2:value2 ...]
    pointer to above:   &{}, &[], &map[]
    Other flags:
    '+'	always print a sign for numeric values;
    	guarantee ASCII-only output for %q (%+q)
    '-'	pad with spaces on the right rather than the left (left-justify the field)
    '#'	alternate format: add leading 0b for binary (%#b), 0 for octal (%#o),
    	0x or 0X for hex (%#x or %#X); suppress 0x for %p (%#p);
    	for %q, print a raw (backquoted) string if strconv.CanBackquote
    	returns true;
    	always print a decimal point for %e, %E, %f, %F, %g and %G;
    	do not remove trailing zeros for %g and %G;
    	write e.g. U+0078 'x' if the character is printable for %U (%#U).
    ' '	(space) leave a space for elided sign in numbers (% d);
    	put spaces between bytes printing strings or slices in hex (% x, % X)
    '0'	pad with leading zeros rather than spaces;
    	for numbers, this moves the padding after the sign;
    	ignored for strings, byte slices and byte arrays
  • Width and Precision

    • Width is specified by an optional decimal number immediately preceding the verb.

      If absent, the width is whatever is necessary to represent the value.

    • Precision is specified after the (optional) width by a period followed by a decimal number.

      If no period is present, a default precision is used. A period with no following number specifies a precision of zero.

      %f     default width, default precision
      %9f    width 9, default precision
      %.2f   default width, precision 2
      %9.2f  width 9, precision 2
      %9.f   width 9, precision 0
  • type Stringer

    type Stringer interface {
    	String() string
    }

    Stringer is implemented by any value that has a String method, which defines the "native" format for that value.

    The String method is used to print values passed as an operand to any format that accepts a string or to an unformatted printer such as Print.

    // Animal has a Name and an Age to represent an animal.
    type Animal struct {
    	Name string
    	Age  uint
    }
    
    // String makes Animal satisfy the Stringer interface.
    func (a Animal) String() string {
    	return fmt.Sprintf("%v (%d)", a.Name, a.Age)
    }
    
    func main() {
    	a := Animal{
    		Name: "Gopher",
    		Age:  2,
    	}
    	fmt.Println(a)
    	// Output:
    	// Gopher (2)
    }

Initialization

Constants

  • Constants are declared like variables, but with the const keyword.

  • Constants cannot be declared using the := syntax.

  • Constants are created at compile time, even when defined as locals in functions, and can only be numbers, characters (runes), strings or booleans.

  • Because of the compile-time restriction, the expressions that define them must be constant expressions, evaluatable by the compiler.

  • In Go, enumerated constants are created using the iota enumerator.

    type Weekday int
    
    const (
        Sunday Weekday = iota + 1 // iota: 0 ~ Sunday    : 1
        _                         // iota: 1 ~ iota increased
        // comments               // iota: 1 ~ skip: comment
                                  // iota: 1 ~ skip: empty line
        Monday                    // iota: 2 ~ Monday    : 3
        Tuesday                   // iota: 3 ~ Monday    : 4
        Wednesday                 // iota: 4 ~ Monday    : 5
        Thursday                  // iota: 5 ~ Monday    : 6
        Friday                    // iota: 6 ~ Monday    : 7
        Saturday                  // iota: 7 ~ Monday    : 8
    )
    iota (noun)
    /aɪˈəʊtə/
    /aɪˈəʊtə/
    
    1. [singular] (usually used in negative sentences) an extremely small amount
        There is not one iota of truth (= no truth at all) in the story.
        I don't think that would help one iota.
    2. the 9th letter of the Greek alphabet (I, ι)
    
    ref: https://www.oxfordlearnersdictionaries.com/us/definition/english/iota

Allocation with new and make

  • Go has two allocation primitives, the built-in functions new and make.

    They do different things and apply to different types, which can be confusing, but the rules are simple.

  • new is a built-in function that allocates memory, but unlike its namesakes in some other languages it does not initialize the memory, it only zeros it.

    That is, new(T) allocates zeroed storage for a new item of type T and returns its address, a value of type *T.

    In Go terminology, it returns a pointer to a newly allocated zero value of type T.

    Since the memory returned by new is zeroed, it’s helpful to arrange when designing your data structures that the zero value of each type can be used without further initialization.

    This means a user of the data structure can create one with new and get right to work.

    For example, the documentation for bytes.Buffer states that "the zero value for Buffer is an empty buffer ready to use."

  • The built-in function make(T, args) serves a purpose different from new(T).

    It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T (not *T).

    The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use.

    var p *[]int = new([]int)       // allocates slice structure; *p == nil; rarely useful
    var v  []int = make([]int, 100) // the slice v now refers to a new array of 100 ints
    
    // Unnecessarily complex:
    var p *[]int = new([]int)
    *p = make([]int, 100, 100)
    
    // Idiomatic:
    v := make([]int, 100)

The init function

  • Each source file can define its own niladic init function to set up whatever state is required.

  • Actually each file can have multiple init functions.

  • init is called after all the variable declarations in the package have evaluated their initializers, and those are evaluated only after all the imported packages have been initialized.

package hello

import (
	"fmt"
)

func init() {
	fmt.Print("hello ")
}
package world

import (
	"fmt"
	_ "hello"
)

func init() {
	fmt.Print("world")
}
package main

import (
	"fmt"
	_ "world"
)

const mark = "!"

func init() {
	fmt.Print(mark)
}

func main() {
    // Output:
    // hello world!
}

Zero values

Variables declared without an explicit initial value are given their zero value.

The zero value is:

  • 0 for numeric types,

  • false for the boolean type,

  • "" (the empty string) for strings,

  • nil for the pointers, slices, maps, functions, interfaces, channels,

Concurrency

Race Conditions

  • A race condition is a situation in which the program does not give the correct result for some interleaving of the operations of multiple goroutines.

  • A data race, that is, a particular kind of race condition, occurs whenever two goroutines access the same variable concurrently and at least one of the accesses is a write.

    It follows from this definition that there are three ways to avoid a data race.

    • The first way is not to write the variable.

    • The second way (channels: share memory by communication) to avoid a data race is to avoid accessing the variable from multiple goroutines.

    • The third way (mutual exclusion: sync.Mutex, sync.RWMutex) to avoid a data race is to allow many goroutines to access the variable, but only one at a time.

  • Synchronization is about more than just the order of execution of multiple goroutines; synchronization also affets memory.

Race Detector

  • The race detector (just add the -race flag to your go build, go run, or go test command) studies this steam of events, looking for cases in which one goroutine reads or writes a shared variables that was most recently written by a different goroutine without an intervening synchronization operation.

  • The race detector reports all data races that wre actually executed. However, it can only detect race conditions that occur during a run; it cannot prove that none will ever occur.

    func main() {
    	var wg sync.WaitGroup
    
    	var x, y int
    
    	wg.Add(1)
    	go func() {
    		defer wg.Done()
    		x = 1
    		fmt.Printf("y = %d\n", y)
    	}()
    
    	wg.Add(1)
    	go func() {
    		defer wg.Done()
    		y = 1
    		fmt.Printf("x = %d\n", x)
    	}()
    
    	wg.Wait()
    }
    $ go run -race race.go
    x = 0
    ==================
    WARNING: DATA RACE
    Write at 0x00c0000a6020 by goroutine 7:
      main.main.func1()
          /tmp/race.go:16 +0x8a
    
    Previous read at 0x00c0000a6020 by goroutine 8:
      main.main.func2()
          /tmp/race.go:24 +0xaa
    
    Goroutine 7 (running) created at:
      main.main()
          /tmp/race.go:14 +0x119
    
    Goroutine 8 (finished) created at:
      main.main()
          /tmp/race.go:21 +0x166
    ==================
    ==================
    WARNING: DATA RACE
    Read at 0x00c0000a6028 by goroutine 7:
      main.main.func1()
          /tmp/race.go:17 +0xaa
    
    Previous write at 0x00c0000a6028 by goroutine 8:
      main.main.func2()
          /tmp/race.go:23 +0x8a
    
    Goroutine 7 (running) created at:
      main.main()
          /tmp/race.go:14 +0x119
    
    Goroutine 8 (finished) created at:
      main.main()
          /tmp/race.go:21 +0x166
    ==================
    y = 1
    Found 2 data race(s)
    exit status 66

Happen before

  • Within a single goroutine, reads and writes must behave as if they executed in the order specified by the program.

  • That is, compilers and processors may reorder the reads and writes executed within a single goroutine only when the reordering does not change the behavior within that goroutine as defined by the language specification.

  • Because of this reordering, the execution order observed by one goroutine may differ from the order perceived by another.

    For example, if one goroutine executes a = 1; b = 2;, another might observe the updated value of b before the updated value of a.

  • To specify the requirements of reads and writes, we define happens before, a partial order on the execution of memory operations in a Go program.

    If event e1 happens before event e2, then we say that e2 happens after e1.

    Also, if e1 does not happen before e2 and does not happen after e2, then we say that e1 and e2 happen concurrently.

  • Within a single goroutine, the happens-before order is the order expressed by the program.

  • Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.

    To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages.

Share by communicating

  • Do not communicate by sharing memory; instead, share memory by communicating.

    • Go encourages a different approach in which shared values are passed around on channels and, in fact, never actively shared by separate threads of execution.

    • Only one goroutine has access to the value at any given time. Data races cannot occur, by design.

  • One way to think about this model is to consider a typical single-threaded program running on one CPU.

    • It has no need for synchronization primitives.

    • Now run another such instance; it too needs no synchronization.

    • Now let those two communicate; if the communication is the synchronizer, there’s still no need for other synchronization.

    • Unix pipelines, for example, fit this model perfectly.

    • Although Go’s approach to concurrency originates in Hoare’s Communicating Sequential Processes (CSP), it can also be seen as a type-safe generalization of Unix pipes.

Goroutines

  • A goroutine has a simple model: it is a function executing concurrently with other goroutines in the same address space.

    • It is lightweight, costing little more than the allocation of stack space.

    • And the stacks start small, so they are cheap, and grow by allocating (and freeing) heap storage as required.

  • Goroutines are multiplexed onto multiple OS threads so if one should block, such as while waiting for I/O, others continue to run.

    • Their design hides many of the complexities of thread creation and management.

    • Prefix a function or method call with the go keyword to run the call in a new goroutine. When the call completes, the goroutine exits, silently.

    • The evaluation of f, x, y, and z of go f(x, y, z) happens in the current goroutine and the execution of f happens in the new goroutine.

      package main
      
      import (
      	"fmt"
      	"time"
      )
      
      func main() {
      	// All function values created by this loop “capture”
      	// and share the same variable—an addressable storage location,
      	// not its value at that particular moment.
      	for i := 0; i < 5; i++ {
      		go func() {
      			fmt.Print(i, " ")
      		}()
      	}
      
      	time.Sleep(time.Millisecond)
      
      	fmt.Println()
      
      	for i := 0; i < 5; i++ {
      		i := i
      		go func() {
      			fmt.Print(i, " ")
      		}()
      	}
      
      	time.Sleep(time.Millisecond)
      
      	// Output:
      	// 5 5 5 5 5
      	// 4 0 1 2 3	// ignore the order
      }

Channels

  • Like maps, channels are allocated with make, and the resulting value acts as a reference to an underlying data structure.

    • If an optional integer parameter is provided, it sets the buffer size for the channel.

    • The default is zero, for an unbuffered or synchronous channel.

      ci := make(chan int)            // unbuffered channel of integers
      cj := make(chan int, 0)         // unbuffered channel of integers
      cs := make(chan *os.File, 100)  // buffered channel of pointers to Files
  • Receivers always block until there is data to receive.

  • The sender blocks only until the value has been copied to the buffer;

  • A buffered channel can be used like a semaphore, for instance to limit throughput.

  • The assembly line metaphor (pipeline) is useful one for channels and goroutines.

    // A concurrent prime sieve
    
    package main
    
    // Send the sequence 2, 3, 4, ... to channel 'ch'.
    func Generate(ch chan<- int) {
    	for i := 2; ; i++ {
    		ch <- i // Send 'i' to channel 'ch'.
    	}
    }
    
    // Copy the values from channel 'in' to channel 'out',
    // removing those divisible by 'prime'.
    func Filter(in <-chan int, out chan<- int, prime int) {
    	for {
    		i := <-in // Receive value from 'in'.
    		if i%prime != 0 {
    			out <- i // Send 'i' to 'out'.
    		}
    	}
    }
    
    // The prime sieve: Daisy-chain Filter processes.
    func main() {
    	ch := make(chan int) // Create a new channel.
    	go Generate(ch)      // Launch Generate goroutine.
    	for i := 0; i < 10; i++ {
    		prime := <-ch
    		print(prime, "\n")
    		ch1 := make(chan int)
    		go Filter(ch, ch1, prime)
    		ch = ch1
    	}
    }

Parallelization

  • Be sure not to confuse the ideas of concurrency—​structuring a program as independently executing components—​and parallelism—​executing calculations in parallel for efficiency on multiple CPUs.

  • Although the concurrency features of Go can make some problems easy to structure as parallel computations, Go is a concurrent language, not a parallel one, and not all parallelization problems fit Go’s model.

    package runtime // import "runtime"
    
    func NumCPU() int
        NumCPU returns the number of logical CPUs usable by the current process.
    
        The set of available CPUs is checked by querying the operating system at
        process startup. Changes to operating system CPU allocation after process
        startup are not reflected.
    
    func GOMAXPROCS(n int) int
        GOMAXPROCS sets the maximum number of CPUs that can be executing
        simultaneously and returns the previous setting. If n < 1, it does not
        change the current setting. The number of logical CPUs on the local machine
        can be queried with NumCPU. This call will go away when the scheduler
        improves.

Errors

  • Library routines must often return some sort of error indication to the caller.

  • Go’s multivalue return makes it easy to return a detailed error description alongside the normal return value.

  • It is good style to use this feature to provide detailed error information.

  • By convention, errors have type error, a simple built-in interface.

    type error interface {
        Error() string
    }
  • The simplest way to create an error is by calling errors.New, which return a new error for a given error message.

  • Calls to errors.New are relatively infrequent because there’s a conveninent wrapper function, fmt.Errorf, that does string formatting too.

  • When feasible, error strings should identify their origin, such as by having a prefix naming the operation or package that generated the error.

    For example, in package image, the string representation for a decoding error due to an unknown format is "image: unknown format".

  • Callers that care about the precise error details can use a type switch or a type assertion to look for specific errors and extract details.

Panic

  • There is a built-in function panic that in effect creates a runtime unrecoverable error that will stop the program.

  • The function takes a single argument of arbitrary type—​often a string—​to be printed as the program dies.

    package builtin // import "builtin"
    
    func panic(v interface{})
        The panic built-in function stops normal execution of the current goroutine.
        When a function F calls panic, normal execution of F stops immediately. Any
        functions whose execution was deferred by F are run in the usual way, and
        then F returns to its caller. To the caller G, the invocation of F then
        behaves like a call to panic, terminating G's execution and running any
        deferred functions. This continues until all functions in the executing
        goroutine have stopped, in reverse order. At that point, the program is
        terminated with a non-zero exit code. This termination sequence is called
        panicking and can be controlled by the built-in function recover.

Recover

  • When panic is called, including implicitly for runtime errors such as indexing a slice out of bounds or failing a type assertion,

    • it immediately stops execution of the current function

    • and begins unwinding the stack of the goroutine,

    • running any deferred functions along the way.

    • If that unwinding reaches the top of the goroutine’s stack, the program dies.

  • However, it is possible to use the built-in function recover to regain control of the goroutine and resume normal execution.

  • A call to recover stops the unwinding and returns the argument passed to panic.

    • Because the only code that runs while unwinding is inside deferred functions, recover is only useful inside deferred functions.

        func F() {
        	panic("F: panic.")
        }
      
        func G() {
        	defer func() {
        		e := recover()
        		if e != nil {
        			fmt.Println("G: recover:", e)
        		}
        	}()
      
        	F()
        }
      
        func main() {
        	G()
        	// Output:
        	// G: recover: F: panic.
        }

Testing

  • The go test subcommand is a test driver for Go packages that are organized according to certain conventions.

  • In a package directory, files whose names end with _test.go are not part of the package ordinarily built by go build but are a part of it when built by go test.

  • Within *_test.go files, four kinds of functions are treated specially: tests, fuzzs, benchmarks, and examples.

    • A test function, which is a function whose name begins with Test, exercises some program logic for correct behavior; go test calls the test function and report the result, which is either PASS or FAIL.

    • With fuzzing, random data is run against your test in an attempt to find vulnerabilities or crash-causing inputs.

    • A benchmark function has a name beginning with Benchmark and measures the performance of some operation; go test reports the mean execution time of the operation.

    • And an example function, whose name starts with Example, provides machine-checked documentation.

    func Foo(s string) string {
    	return s
    }
    
    func TestFoo(t *testing.T) {
    	var tests = []struct {
    		s    string
    		want string
    	}{
    		{"Hello", "Hello"},
    		{"世界!", "世界!"},
    	}
    
    	for _, test := range tests {
    		if got := Foo(test.s); got != test.want {
    			t.Errorf("foo(%q) == %q, want %q", test.s, got, test.want)
    		}
    	}
    }
    
    // Fuzz test
    func FuzzFoo(f *testing.F) {
    	// Seed corpus addition
    	f.Add("hello")
    	// Fuzz target
    	f.Fuzz(func(t *testing.T, s string) {
    		// s string // Fuzzing arguments
    
    		if got := Foo(s); got != s {
    			t.Errorf("foo(%q) == %q, want %q", s, got, s)
    		}
    	})
    }
    
    func BenchmarkFoo(b *testing.B) {
    	for n := 0; n < b.N; n++ {
    	}
    }
    
    func ExampleFoo() {
    	fmt.Println("BAR")
    	// Output:
    	// BAR
    }
    $ GO111MODULE=off go test
    PASS
    ok  	_/tmp/learn-notes	0.003s
    
    $ GO111MODULE=off go test -fuzz=Fuzz -fuzztime=3s
    fuzz: elapsed: 0s, gathering baseline coverage: 0/1 completed
    fuzz: elapsed: 0s, gathering baseline coverage: 1/1 completed, now fuzzing with 4 workers
    fuzz: elapsed: 3s, execs: 226192 (75387/sec), new interesting: 0 (total: 1)
    fuzz: elapsed: 3s, execs: 226192 (0/sec), new interesting: 0 (total: 1)
    PASS
    ok  	_/tmp/learn-notes	3.127s
    
    $ GO111MODULE=off go test -bench=.*
    goos: linux
    goarch: amd64
    cpu: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz
    BenchmarkFoo-4   	1000000000	         0.5349 ns/op
    PASS
    ok  	_/tmp/learn-notes	0.605s

Modules

// In Go, if an old package and a new package have the same import path,
// the new package must be backwards compatible with the old package.
// There is certainly a cost to needing to introduce a new name for each backwards-incompatible API change,
// but as the semver FAQ says, that cost should encourage authors to more clearly consider
// the impact of such changes and whether they are truly necessary.
  • A module is a collection of related Go packages that are versioned together as a single unit.

  • Modules record precise dependency requirements and create reproducible builds.

  • Most often, a version control repository contains exactly one module defined in the repository root.

  • Summarizing the relationship between repositories, modules, and packages:

    • A repository contains one or more Go modules.

    • Each module contains one or more Go packages.

    • Each package consists of one or more Go source files in a single directory.

  • Modules must be semantically versioned according to semver, usually in the form v(major).(minor).(patch), such as v0.1.0, v1.2.3, or v1.5.0-rc.1.

    • The leading v is required.

    • If using Git, tag released commits with their versions.

  • A module is defined by a tree of Go source files with a go.mod file in the tree’s root directory.

  • A module declares its identity in its go.mod via the module directive, which provides the module path.

    • The import paths for all packages in a module share the module path as a common prefix.

    • The module path and the relative path from the go.mod to a package’s directory together determine a package’s import path.

  • In Go source code, packages are imported using the full path including the module path.

$ go help modules
$ go help go.mod
$ go help module-private
$ go help goproxy
$ go env GOPROXY # https://proxy.golang.org,direct
$ go env -w GOPROXY=https://goproxy.cn,direct
$ go env GOPROXY # https://goproxy.cn,direct
$ go help gopath