1. 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
}

2. 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()

3. Names

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

3.1. 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.

3.2. 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)
    }

3.3. 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.

3.4. MixedCaps

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

4. 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.

5. 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.

5.1. If

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

5.2. 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]
}

5.3. 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)
        }
    }

5.3.1. 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
}

5.4. 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)
		}
	}
}

5.5. 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
		}
	}
}

5.6. 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.

switch {
case 10 > 11:
	fmt.Println("10 > 11")
case 1 < 5:
	fmt.Println("1 < 5")
	fallthrough
case 1 > 10:
	fmt.Println("1 > 10 ?")
}
// Output:
// 1 < 5
// 1 > 10 ?

5.7. 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,

    godir, err := os.Open("/usr/local/go")
    if err != nil {
    	log.Printf("%s\n", err)
    	defer godir.Close()
    }
  • or because the corresponding goroutine is panicking.

    	defer func() {
    		e := recover()
    		fmt.Printf("recover: %s\n", e)
    	}()
    
    	defer func() {
    		fmt.Println(". . .")
    	}()
    
    	panic(fmt.Sprintf("Oops, I'm NOT myself."))
    	// Output:
    	// . . .
    	// recover: Oops, I'm NOT myself.
  • Go’s defer statement schedules a function call (the deferred function) to be run immediately before the function executing the defer returns.

    // 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.
    func main() {
    	v := 10
    	defer fmt.Println(3 * v) // 30
    
    	defer func() {
    		fmt.Println(v) // 20
    	}()
    
    	defer func(x int) {
    		fmt.Println(x) // 10
    	}(v)
    
    	v = 20
    	_ = v
    }
    
    // Output:
    // 10
    // 20
    // 30
  • 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)
    }
  • 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

6. Data types

// any is an alias for interface{} and is equivalent to interface{} in all ways.
// (go1.18)
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.
// (go1.18)
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

6.1. 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.

const nihongo = "日本語"
for _, runeValue := range nihongo {
	fmt.Printf("%#U ", runeValue)
}
fmt.Println()
// U+65E5 '日' U+672C '本' U+8A9E '語'

for index := 0; index < len(nihongo); index++ {
	fmt.Printf("%x ", nihongo[index])
}
fmt.Println()
// e6 97 a5 e6 9c ac e8 aa 9e

for index := 0; index < len(nihongo); index++ {
	fmt.Printf("%q ", nihongo[index])
}
fmt.Println()
// 'æ' '\u0097' '¥' 'æ' '\u009c' '¬' 'è' 'ª' '\u009e'

for index := 0; index < len(nihongo); index++ {
	fmt.Printf("%+q ", nihongo[index])
}
fmt.Println()
// '\u00e6' '\u0097' '\u00a5' '\u00e6' '\u009c' '\u00ac' '\u00e8' '\u00aa' '\u009e'

6.2. 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

6.3. 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)
}

6.4. 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}

6.5. 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)

6.6. 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 that is comparable for which the equality operator is defined.

    The language spec defines the Comparison operators precisely, in short, comparable types are boolean, numeric, string, pointer, channel, and interface types, and structs or arrays that contain only those types.[BLOGMAPS]

    Notably absent from the list are slices, maps, and functions; these types cannot be compared using ==, and may not be used as map keys.

  • 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"]

6.7. Functions

  • A function can return any number of results.

    func (file *File) Write(b []byte) (n int, err error)
  • 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.

  • 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, that is a function value that references (i.e. bounds to) variables from outside its body.

    func adder() func(int) int {
    	sum := 0
    	return func(x int) int {
    		sum += x
    		return sum
    	}
    }
    
    func w(s func(int) int, i int) int {
    	return s(i)
    }
    
    func main() {
    	pos, neg := adder(), adder()
    	for i := 1; i <= 3; i++ {
    		fmt.Printf("%+d, %+2d\n", w(pos, i), neg(-i))
    	}
    }
    
    // Output:
    // +1, -1
    // +3, -3
    // +6, -6
  • A function literal represents an anonymous function and cannot declare type parameters, and it can be assigned to a variable or invoked directly.

  • Function literals are closures: they may refer to variables defined in a surrounding function, which are then shared between the surrounding function and the function literal, and they survive as long as they are accessible.

    func main() {
    	var sum int
    	var add = func() {
    		sum += 1
    	}
    	add()
    	fmt.Printf("%d\n", sum)
    }
    
    // Output:
    // 1

6.8. Methods

A method is a function with a special receiver argument, defined on any named type (except a pointer or an interface) in the same package.

type MyInt int

func (mi *MyInt) String() string {
	return fmt.Sprintf("Hi %d!", *mi)
}

func (mi *MyInt) Add(delta int) {
	*mi = *mi + MyInt(delta)
}

func main() {
	var mi MyInt = 1_024
	mi.Add(1_024)
	fmt.Println(&mi)
}

// Output:
// Hi 2048!

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, that 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.

    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.

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.

    package bytes // import "bytes"
    
    type Buffer struct {
    	// Has unexported fields.
    }
        A Buffer is a variable-sized buffer of bytes with Read and Write methods.
        The zero value for Buffer is an empty buffer ready to use.

6.9. 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:

    An interface value holds a value of a specific underlying concrete type.

    (value, 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 receiver>")
    		return
    	}
    }
    
    func main() {
    	var i I // `i` is nil
    	// i.M() // runtime error: invalid memory address or nil pointer dereference
    	var t *T
    	i = t // `i` is not nil, but the concrete type `t` is nil
    	i.M()
    	fmt.Printf("%v, %T\n", i, i)
    
    	i = &T{} // the concrete type `t` is not nil
    	i.M()
    	fmt.Printf("%v, %T\n", i, i)
    }
    
    // Output:
    // <nil receiver>
    // <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.

Basic interfaces

  • Interfaces whose type sets can be defined entirely by a list of methods are called basic interfaces.

    // A simple File interface.
    interface {
    	Read([]byte) (int, error)
    	Write([]byte) (int, error)
    	Close() error
    }

Embedded interfaces

  • In a slightly more general form an interface T may use a (possibly qualified) interface type name E as an interface element, which is called embedding interface E in T.

  • The type set of T is the intersection of the type sets defined by T’s explicitly declared methods and the type sets of T’s embedded interfaces.

    In other words, the type set of T is the set of all types that implement all the explicitly declared methods of T and also all the methods of E.

    type Reader interface {
    	Read(p []byte) (n int, err error)
    	Close() error
    }
    
    type Writer interface {
    	Write(p []byte) (n int, err error)
    	Close() error
    }
    
    // ReadWriter's methods are Read, Write, and Close.
    type ReadWriter interface {
    	Reader  // includes methods of Reader in ReadWriter's method set
    	Writer  // includes methods of Writer in ReadWriter's method set
    }
  • When embedding interfaces, methods with the same names must have identical signatures.

    type ReadCloser interface {
    	Reader   // includes methods of Reader in ReadCloser's method set
    	Close()  // illegal: signatures of Reader.Close and Close are different
    }

General interfaces

Azure AI | ChatGPT 4

In Go, general interfaces are used to define type constraints for type parameters in generic functions and data structures. [ChatGpt4]

// Comparable is an interface that defines a type constraint using
// a union of types (int, float64, and string). This means that a
// type parameter satisfying the Comparable constraint must be one
// of these types.
type Comparable interface {
	int | float64 | string
}

// The Max function is defined as a generic function using the type
// parameter T. The type parameter is specified within square brackets
// ([T Comparable]). It means that the function can work with any type
// T that satisfies the Comparable constraint.
func Max[T Comparable](a, b T) T {
	if a > b {
		return a
	}
	return b
}

// The Max function is called with different types of arguments (int,
// float64, and string). The type parameter T is replaced with the actual
// type of the arguments at each call, allowing the function to work with
// different types while maintaining type safety.
func main() {
	fmt.Println(Max(3, 4))           // int
	fmt.Println(Max(2.5, 3.7))       // float64
	fmt.Println(Max("apple", "cat")) // string
}

In summary, while you can’t assign a concrete type to a general interface, general interfaces are used to specify what types can be used with a generic function or data structure.

These type constraints allow you to create flexible and reusable generic code while maintaining type safety.

  • 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.
    interface {
    	int
    }
    
    // An interface representing all types with underlying type int.
    interface {
    	~int
    }
    
    // An interface representing all types with underlying type int that implement the String method.
    interface {
    	~int
    	String() string
    }
    
    // An interface representing an empty type set: there is no type that is both an int and a string.
    //
    // This code defines an interface that no concrete type satisfies because there is no type that is
    // both an int and a string. It is not the same as an empty interface (interface{}), which any type
    // can satisfy. This code snippet is used to illustrate the concept of an unsatisfiable interface
    // in the Go language specification. (Azure AI | ChatGPT 4)
    //
    // While this interface can be compiled, it cannot be used in practical terms because no type can
    // satisfy the constraints. It's a theoretical construct to show the capabilities and limitations of
    // the type constraint system in Go. (Azure AI | ChatGPT 4)
    interface {
    	int
    	string
    }
  • In a term of the form ~T, the underlying type of T must be itself, and T cannot be an interface.

    Azure AI | ChatGPT 4

    The tilde symbol ~ defines a type set constraint that includes the underlying type of T as well as any other types whose underlying type is the same as T.

    In this context, "underlying type" refers to the actual type without any type aliases or defined types based on it.

    • "The underlying type of T must be itself": This means that when defining a type set constraint using ~T, the type T must not be a type alias or a defined type based on another type. Instead, T must be a "base" type, such as int, float64, or a struct type.

    • "T cannot be an interface": This condition states that the type T in a type set constraint using ~T should not be an interface type. This is because interface types don’t have a specific underlying type that can be used in the type set definition.

    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
    }

6.10. 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
    }
  • For the embedded 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()
    	// Output:
    	// 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.

Embedding:

  • Provides an "is-a" relationship where the outer struct or interface directly inherits the fields and methods of the embedded struct or interface.

  • Allows for easier and more direct access to the fields and methods of the embedded struct or interface, as they become part of the outer struct or interface.

  • Enhances code reuse and polymorphism by making the fields and methods of the embedded struct or interface available directly in the outer struct or interface.

Not Embedding (Composing):

  • Provides a "has-a" relationship where the outer struct or interface holds instances of other structs or interfaces as separate fields.

  • Requires explicitly accessing the fields and methods of the inner structs or interfaces through the composed fields.

  • Keeps a clear separation between the fields and methods of the outer struct or interface and the inner structs or interfaces it holds.

    type Reader struct {
    }
    
    func (r *Reader) Read() {
    	fmt.Printf("Read.\n")
    }
    
    type Writer struct {
    }
    
    func Write() {
    	fmt.Printf("Write.\n")
    }
    
    type ReadWriter struct {
    	reader *Reader
    	writer *Writer
    }
    
    func main() {
    	rw := &ReadWriter{&Reader{}, &Writer{}}
    	rw.reader.Read() // Output: Read.
    	rw.Read()        // Compiler error: rw.Read undefined (type *ReadWriter has no field or method Read)
    }

6.11. 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)
    	}
    }

6.12. 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)

6.13. 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.

    package builtin // import "builtin"
    
    type comparable interface{ comparable }
        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.
  • 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.

    type ComparableOrdered interface {
    	comparable
    	constraints.Ordered // "golang.org/x/exp/constraints"
    }
    
    // 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
    }
  • Type constraint and type parameter

    • A type constraint is an interface that defines the set of permissible type arguments for the respective type parameter and controls the operations supported by values of that type parameter.

    • If the constraint is an interface literal of the form interface{E} where E is an embedded type element (not a method), in a type parameter list the enclosing interface{ … } may be omitted for convenience:

      [T []P]                      // = [T interface{[]P}]
      [T ~int]                     // = [T interface{~int}]
      [T int|string]               // = [T interface{int|string}]
      type Constraint ~int         // illegal: ~int is not in a type parameter list
    • A type argument T satisfies a type constraint C if T is an element of the type set defined by C; i.e., if T implements C.

      As an exception, a strictly comparable type constraint may also be satisfied by a comparable (not necessarily strictly comparable) type argument. More precisely: A type T satisfies a constraint C if

      • T implements C; or

      • C can be written in the form interface{ comparable; E }, where E is a basic interface and T is comparable and implements E.

      type argument      type constraint                // constraint satisfaction
      
      int                interface{ ~int }              // satisfied: int implements interface{ ~int }
      string             comparable                     // satisfied: string implements comparable (string is strictly comparable)
      []byte             comparable                     // not satisfied: slices are not comparable
      any                interface{ comparable; int }   // not satisfied: any does not implement interface{ int }
      any                comparable                     // satisfied: any is comparable and implements the basic interface any
      struct{f any}      comparable                     // satisfied: struct{f any} is comparable and implements the basic interface any
      any                interface{ comparable; m() }   // not satisfied: any does not implement the basic interface interface{ m() }
      interface{ m() }   interface{ comparable; m() }   // satisfied: interface{ m() } is comparable and implements the basic interface interface{ m() }

      Because of the exception in the constraint satisfaction rule, comparing operands of type parameter type may panic at run-time (even though comparable type parameters are always strictly comparable).

    • A type parameter list declares the type parameters of a generic function or type declaration.

      The type parameter list looks like an ordinary function parameter list except that the type parameter names must all be present and the list is enclosed in square brackets rather than parentheses.

      TypeParameters  = "[" TypeParamList [ "," ] "]" .
      TypeParamList   = TypeParamDecl { "," TypeParamDecl } .
      TypeParamDecl   = IdentifierList TypeConstraint .
    • All non-blank names in the type parameter list must be unique.

      • Each name declares a type parameter, which is a new and different named type that acts as a placeholder for an (as of yet) unknown type in the declaration.

      • The type parameter is replaced with a type argument upon instantiation of the generic function or type.

        [P any]
        [S interface{ ~[]byte|string }]
        [S ~[]E, E any]
        [P Constraint[int]]
        [_ any]
    • As the ordinary function parameter has a parameter type, the type parameter has a (meta-)type which is called its type constraint.

7. Initialization

7.1. 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

7.2. Allocation with new and make

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

  • 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.

    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.
    • 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).

    package builtin // import "builtin"
    
    func make(t Type, size ...IntegerType) Type
        The make built-in function allocates and initializes an object of type
        slice, map, or chan (only). Like new, the first argument is a type, not a
        value. Unlike new, make's return type is the same as the type of its
        argument, not a pointer to it. The specification of the result depends on
        the type:
    
            Slice: The size specifies the length. The capacity of the slice is
            equal to its length. A second integer argument may be provided to
            specify a different capacity; it must be no smaller than the
            length. For example, make([]int, 0, 10) allocates an underlying array
            of size 10 and returns a slice of length 0 and capacity 10 that is
            backed by this underlying array.
    
            Map: An empty map is allocated with enough space to hold the
            specified number of elements. The size may be omitted, in which case
            a small starting size is allocated.
    
            Channel: The channel's buffer is initialized with the specified
            buffer capacity. If zero, or the size is omitted, the channel is
            unbuffered.
    • 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)

7.3. 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, which called in the order they appear in the source.

  • 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.

    $ tree
    .
    ├── go.mod
    ├── hello
    │   └── hello.go
    ├── init.go
    └── world
        └── world.go
    
    3 directories, 4 files
    $ cat go.mod
    module hello.world/init
    
    go 1.18
    $ cat hello/hello.go
    package hello
    
    import "fmt"
    
    func init() {
    	fmt.Printf("Hello")
    }
    $ cat world/world.go
    package world
    
    import "fmt"
    
    func init() {
    	fmt.Printf(", ")
    }
    
    func init() {
    	fmt.Printf("世界")
    }
    
    func init() {
    	fmt.Printf("!\n")
    }
    $ cat init.go
    package main
    
    import (
    	_ "hello.world/init/hello"
    	_ "hello.world/init/world"
    )
    
    func main() {
    }
    $ go run init.go
    Hello, 世界!

7.4. 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,

8. Concurrency

8.1. 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.

    Azure Open AI | ChatGPT4

    Other race conditions include deadlocks, livelocks, and starvation.

    Deadlocks occur when two or more processes are waiting for each other to release resources that they need to continue executing.

    Livelocks occur when two or more processes are constantly changing their state in response to the actions of the other process, without making any progress towards their goal.

    A livelock is a situation where two or more processes or threads are actively trying to resolve a conflict but end up repeatedly yielding to each other, resulting in no progress being made. Livelocks are similar to deadlocks in that they both involve processes being stuck, but livelocks involve active, ongoing attempts to resolve the problem, whereas deadlocks involve processes waiting for resources.

    Here’s an example of a livelock:

    Imagine two people, Alice and Bob, walking in a narrow corridor towards each other. When they meet in the middle, they both step aside to let the other person pass. However, they both step in the same direction. Realizing this, they both step back to their original positions and try again, but they continue to step in the same direction each time. This results in a livelock, as they are both actively trying to resolve the situation but end up yielding to each other without making progress.

    Starvation occurs when a process is prevented from accessing the resources it needs to execute, either because other processes are monopolizing those resources or because the system is not allocating resources fairly.

    • 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.

8.2. 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.
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    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 0x00c0000160c8 by goroutine 7:
      main.main.func1()
          /home/x/learn/go/race.go:15 +0xaa
    
    Previous read at 0x00c0000160c8 by goroutine 8:
      main.main.func2()
          /home/x/learn/go/race.go:23 +0xcf
    ...
    ==================
    ==================
    WARNING: DATA RACE
    Read at 0x00c0000160d8 by goroutine 7:
      main.main.func1()
          /home/x/learn/go/race.go:16 +0xcf
    
    Previous write at 0x00c0000160d8 by goroutine 8:
      main.main.func2()
          /home/x/learn/go/race.go:22 +0xaa
    ==================
    y = 1
    Found 2 data race(s)
    exit status 66
Azure Open AI | ChatGPT4

When you run a Go program with the -race flag, the Go race detector instruments the code to monitor concurrent access to shared variables. If it detects a race condition, it generates a report with detailed information about the issue. The output of the -race flag typically consists of the following parts:

  1. A summary of the data race, indicating that it is a "DATA RACE."

  2. A description of the current operation (read or write) that caused the race, including the memory address, the source code file, and the line number.

  3. The current goroutine involved in the race, including its number and the stack trace.

  4. A description of the previous conflicting operation (read or write) on the same memory address, including the source code file and the line number.

  5. The previous goroutine involved in the race, including its number and the stack trace.

For example, consider the following output:

WARNING: DATA RACE
Write at 0x00c0000160c8 by goroutine 7:
  main.main.func1()
      /path/to/your/code/main.go:16 +0x6a

Previous read at 0x00c0000160c8 by goroutine 8:
  main.main.func2()
      /path/to/your/code/main.go:22 +0x3e

This output can be interpreted as follows:

  1. The race detector has identified a data race.

  2. A write operation occurred at memory address 0x00c0000160c8 by goroutine 7. The operation happened in the function main.main.func1() in the source code file main.go at line 16.

  3. The current goroutine 7 is shown along with the stack trace.

  4. A previous read operation occurred at the same memory address 0x00c0000160c8 by goroutine 8. The operation happened in the function main.main.func2() in the source code file main.go at line 22.

  5. The previous goroutine 8 is shown along with the stack trace.

Using this information, you can identify the source code lines and goroutines involved in the race condition and take appropriate action to fix the issue, such as adding proper synchronization mechanisms like mutexes or channels.

8.3. 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.

8.4. 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.

8.5. 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. [TALKSCONCURRENCY]

    • 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.

      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
      }

Best number of goroutines in a process

There is no one-size-fits-all answer to the best number of goroutines in a process, as it depends on various factors such as the nature of your application, the resources available on your system, and the level of parallelism your program can achieve. However, here are some general guidelines to help you determine the optimal number of goroutines: [Azure Open AI | ChatGPT4]

  • I/O-bound tasks: If your application performs tasks that are primarily I/O-bound, such as reading from or writing to disk, network, or other external resources, you can benefit from a higher number of goroutines. Since I/O-bound tasks often involve waiting for external resources, having more goroutines can help keep your application busy and utilize available CPU resources effectively. In this case, the optimal number of goroutines might be several times the number of available CPU cores.

  • CPU-bound tasks: If your application performs tasks that are primarily CPU-bound, such as complex calculations or data processing, you may not benefit from a higher number of goroutines than the number of available CPU cores. Having more goroutines than CPU cores can lead to frequent context switching, which can hurt performance. In this case, the optimal number of goroutines might be close to the number of available CPU cores.

  • Workload characteristics: The best number of goroutines also depends on the specific characteristics of your application’s workload. For example, if your application has a mix of I/O-bound and CPU-bound tasks, or if it has varying resource requirements over time, you might need to experiment with different numbers of goroutines to find the optimal balance.

  • Resource availability: The optimal number of goroutines also depends on the resources available on your system, such as CPU, memory, and I/O capacity. If your system is constrained in terms of resources, you may need to limit the number of goroutines to avoid exhausting system resources and causing performance issues.

When there are many Go processes running on the same operating system, it is essential to consider the overall resource usage of the entire system. Running multiple Go processes with a large number of goroutines can lead to contention for system resources, such as CPU, memory, and I/O capacity. In this case, it might be necessary to limit the number of goroutines per process or to distribute the processes across multiple machines to ensure optimal performance.

Ultimately, the best way to determine the optimal number of goroutines for your application is through benchmarking, monitoring, and profiling. By measuring the performance of your application under different conditions and configurations, you can make informed decisions about the appropriate number of goroutines and resource allocation.

8.6. 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
    
    // 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
    	}
    }

8.7. 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.

8.8. Synchronization

In Go, synchronization is essential for ensuring safe concurrent execution of code and coordinating the activities of multiple goroutines. Go provides various synchronization primitives and constructs to facilitate communication between goroutines, protect access to shared resources, and manage concurrent execution. Here’s a comprehensive introduction to synchronization in Go: [Azure Open AI | ChatGPT4]

8.8.1. Channels

Channels are the primary synchronization mechanism in Go and are used for communication between goroutines. They allow goroutines to send and receive values, which can be used for signaling or passing data. Channels provide a way to synchronize the execution of goroutines and ensure that data is safely shared without race conditions.

// Creating a channel
ch := make(chan int)

// Sending a value to a channel
ch <- 42

// Receiving a value from a channel
value := <-ch
  • Semaphores in Go?

    Go does not provid semaphores as a dedicated primitive like sync.Mutex . Instead, they can be implemented using buffered channels.

    Semaphores are used to control access to a limited number of resources or to limit the degree of concurrency for a group of goroutines.

    Here’s an example of how to implement a semaphore using a buffered channel in Go:

    const maxConcurrent = 3
    
    func main() {
    	var wg sync.WaitGroup
    	// Create a buffered channel to act as a semaphore
    	semaphore := make(chan struct{}, maxConcurrent)
    	// Simulate 10 tasks that need to be executed concurrently
    	for i := 1; i <= 10; i++ {
    		wg.Add(1)
    		go func(taskID int) {
    			defer wg.Done()
    			// Acquire the semaphore
    			semaphore <- struct{}{}
    			// Execute the task
    			fmt.Printf("Task %d started\n", taskID)
    			time.Sleep(1 * time.Second)
    			fmt.Printf("Task %d completed\n", taskID)
    			// Release the semaphore
    			<-semaphore
    		}(i)
    	}
    	wg.Wait()
    }

8.8.2. sync.Mutex

A Mutex (short for "mutual exclusion", /ˈmjuːtɛks/) is a synchronization primitive used to protect access to shared resources and ensure that only one goroutine can access the resource at a time.

Mutexes help prevent race conditions when multiple goroutines attempt to modify shared state concurrently.

// Defining a Mutex
var mu sync.Mutex

// Locking and unlocking a Mutex
mu.Lock()
// access shared resource
mu.Unlock()

8.8.3. sync.RWMutex

An RWMutex (short for "read-write mutex") is similar to a Mutex but allows multiple readers to access a shared resource simultaneously, as long as no writer is accessing it.

It provides two separate locks: a read lock and a write lock. Multiple goroutines can acquire read locks at the same time, but only one can acquire a write lock, and it must wait for all read locks to be released.

// Defining an RWMutex
var rwMu sync.RWMutex

// Locking and unlocking for reading
rwMu.RLock()
// read shared resource
rwMu.RUnlock()

// Locking and unlocking for writing
rwMu.Lock()
// write to shared resource
rwMu.Unlock()

8.8.4. sync.WaitGroup

A WaitGroup is a synchronization construct used to wait for a collection of goroutines to finish execution, that is often used when you start multiple goroutines to perform tasks concurrently and need to wait for all of them to complete before proceeding.

// Defining a WaitGroup
var wg sync.WaitGroup

// Incrementing the WaitGroup counter
wg.Add(1)

// Decrementing the WaitGroup counter (usually in a goroutine)
wg.Done()

// Waiting for all goroutines to finish
wg.Wait()

8.8.5. sync.Cond

A Cond (short for "condition") is a synchronization primitive used to wait for or signal conditions.

It is useful when one or more goroutines need to wait for a specific condition to be met before they can proceed.

Conds are often used with a Mutex or RWMutex to protect access to the shared state being tested in the condition.

// Defining a Cond with a Mutex
c := sync.NewCond(&sync.Mutex{})

// Waiting for a condition
c.L.Lock()
// check condition
c.Wait()
c.L.Unlock()

// Signaling a single waiting goroutine that the condition has been met
c.Signal()

// Broadcasting to all waiting goroutines that the condition has been met
c.Broadcast()
type Queue struct {
	data []int
	cond *sync.Cond
}

func NewQueue() *Queue {
	return &Queue{
		data: make([]int, 0),
		cond: sync.NewCond(&sync.Mutex{}),
	}
}

func (q *Queue) Enqueue(item int) {
	q.cond.L.Lock()
	q.data = append(q.data, item)
	q.cond.L.Unlock()

	q.cond.Signal() // Signal that an item has been added to the queue
}

func (q *Queue) Dequeue() int {
	q.cond.L.Lock()
	for len(q.data) == 0 {
		q.cond.Wait() // Wait for an item to be added to the queue
	}

	item := q.data[0]
	q.data = q.data[1:]
	q.cond.L.Unlock()

	return item
}

8.8.6. sync.Once

A Once is a synchronization construct used to ensure that a function is only executed once, regardless of how many goroutines attempt to call it, that is useful for initializing shared resources, such as global variables or singletons, in a concurrent environment.

// Defining a Once
var once sync.Once

// Executing a function only once, regardless of how many goroutines call it
once.Do(func() {
    // initialize shared resource
})

8.8.7. sync.Map

A Map is a concurrent, thread-safe map implementation provided by the sync package. It is designed for cases where the number of keys is large and their lifetimes are mostly unknown.

Unlike the built-in map type, sync.Map provides safe concurrent access without requiring an additional synchronization mechanism like a Mutex.

// Defining a sync.Map
var m sync.Map

// Storing a value in sync.Map
m.Store("key", "value")

// Loading a value from sync.Map
value, ok := m.Load("key")

// Deleting a value from sync.Map
m.Delete("key")

// Iterating over sync.Map
m.Range(func(key, value interface{}) bool {
    fmt.Printf("key: %v, value: %v\n", key, value)
    return true
})

8.8.8. sync/atomic

package atomic // import "sync/atomic"

Package atomic provides low-level atomic memory primitives useful for
implementing synchronization algorithms.

These functions require great care to be used correctly. Except for special,
low-level applications, synchronization is better done with channels or the
facilities of the sync package. Share memory by communicating; don't
communicate by sharing memory.

9. Errors

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

  • Go’s multi-value 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.

9.1. 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.

9.2. 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.
      }

10. 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

11. Modules

Go 1.11 and 1.12 include preliminary support for modules, Go’s new dependency management system that makes dependency version information explicit and easier to manage.[BLOGSMODULES]

GOPATH and GO111MODULE: New module changes in Go 1.16

The go command now builds packages in module-aware mode by default, even when no go.mod is present. This is a big step toward using modules in all projects.

It’s still possible to build packages in GOPATH mode by setting the GO111MODULE environment variable to off. You can also set GO111MODULE to auto to enable module-aware mode only when a go.mod file is present in the current directory or any parent directory. This was previously the default. Note that you can set GO111MODULE and other variables permanently with go env -w:

go env -w GO111MODULE=auto

We plan to drop support for GOPATH mode in Go 1.17. In other words, Go 1.17 will ignore GO111MODULE. If you have projects that do not build in module-aware mode, now is the time to migrate.

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 path is the canonical name for a module, declared with the module directive in the module’s go.mod file. A module’s path is the prefix for package paths within the module.

A module path should describe both what the module does and where to find it. Typically, a module path consists of a repository root path, a directory within the repository (usually empty), and a major version suffix (only for major version 2 or higher). [REFMOD]

  • 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

12. Printf

Package fmt implements formatted I/O with functions analogous to C’s printf and scanf.[PKGFMT]

12.1. 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
type Pointer struct {
	X int
	Y int
}

func main() {
	p := Pointer{3, 4}
	fmt.Printf("%%v: %v\n", p)
	fmt.Printf("%%+v: %+v\n", p)
	fmt.Printf("%%#v: %#v\n", p)
	fmt.Printf("%%T: %T\n", p)
}

// Output:
// %v: {3 4}
// %+v: {X:3 Y:4}
// %#v: main.Pointer{X:3, Y:4}
// %T: main.Pointer
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"
func main() {
	n := 1234
	fmt.Printf("%%b: %b\n", n)
	fmt.Printf("%%c: %c\n", n)
	fmt.Printf("%%d: %d\n", n)
	fmt.Printf("%%o: %o\n", n)
	fmt.Printf("%%O: %O\n", n)
	fmt.Printf("%%q: %q\n", n)
	fmt.Printf("%%x: %x\n", n)
	fmt.Printf("%%X: %X\n", n)
	fmt.Printf("%%U: %U\n", n)
}

// Output:
// %b: 10011010010
// %c: Ӓ
// %d: 1234
// %o: 2322
// %O: 0o2322
// %q: 'Ӓ'
// %x: 4d2
// %X: 4D2
// %U: U+04D2
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
func main() {
	n := -123456.789
	fmt.Printf("%%b: %b\n", n)
	fmt.Printf("%%e: %e\n", n)
	fmt.Printf("%%E: %E\n", n)
	fmt.Printf("%%f: %f\n", n)
	fmt.Printf("%%F: %F\n", n)
	fmt.Printf("%%g: %g\n", n)
	fmt.Printf("%%G: %G\n", n)
	fmt.Printf("%%x: %x\n", n)
	fmt.Printf("%%X: %X\n", n)
}

// Output:
// %b: -8483885939586761p-36
// %e: -1.234568e+05
// %E: -1.234568E+05
// %f: -123456.789000
// %F: -123456.789000
// %g: -123456.789
// %G: -123456.789
// %x: -0x1.e240c9fbe76c9p+16
// %X: -0X1.E240C9FBE76C9P+16
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
func main() {
	n := 123
	fmt.Printf("%+06d\n", n)
}

// Output:
// +00123

12.2. 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

12.3. 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)
}