Golang Learning Notes
Foramtting
With Go we take an unusual approach and let the machine take care of most formatting issues. The gofmt
program (also available as go fmt
, which operates at the package level rather than source file level) reads a Go program and emits the source in a standard style of indentation and vertical alignment, retaining and if necessary reformatting comments.
$ cat formatting.go && gofmt formatting.go
package formatting
type T struct {
name string // name of the object
value int // its value
}
package formatting
type T struct {
name string // name of the object
value int // its value
}
Commentary
Go provides C-style /* */
block comments and C++-style //
line comments. Line comments are the norm; block comments appear mostly as package comments, but are useful within an expression or to disable large swaths of code.
The program—and web server--godoc
processes Go source files to extract documentation about the contents of the package. Comments that appear before top-level declarations, with no intervening newlines, are extracted along with the declaration to serve as explanatory text for the item. The nature and style of these comments determines the quality of the documentation godoc produces.
/*
Package regexp implements a simple library for regular expressions.
The syntax of the regular expressions accepted is:
regexp:
concatenation { '|' concatenation }
concatenation:
{ closure }
closure:
term [ '*' | '+' | '?' ]
term:
'^'
'$'
'.'
character
'[' [ '^' ] character-ranges ']'
'(' regexp ')'
*/
package regexp
$ go doc builtin new
package builtin // import "builtin"
func new(Type) *Type
The new built-in function allocates memory. The first argument is a type,
not a value, and the value returned is a pointer to a newly allocated zero
value of that type.
$ go doc sync Mutex
package sync // import "sync"
type Mutex struct {
// Has unexported fields.
}
A Mutex is a mutual exclusion lock. The zero value for a Mutex is an
unlocked mutex.
A Mutex must not be copied after first use.
func (m *Mutex) Lock()
func (m *Mutex) Unlock()
Names
The visibility of a name outside a package is determined by whether its first character is upper case.
Package names
-
By convention, packages are given lower case, single-word names; there should be no need for underscores or mixedCaps.
-
Another convention is that the package name is the base name of its source directory; the package in
src/encoding/base64
is imported as "encoding/base64
" but has namebase64
, notencoding_base64
and notencodingBase64
. -
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 calledReader
, notBufReader
, because users see it asbufio.Reader
, which is a clear, concise name. -
Moreover, because imported entities are always addressed with their package name,
bufio.Reader
does not conflict withio.Reader
. -
Similarly, the function to make new instances of
ring.Ring
—which is the definition of a constructor in Go—would normally be calledNewRing
, but sinceRing
is the only type exported by the package, and since the package is calledring
, it’s called justNew
, which clients of the package see asring.New
.
-
Getters
-
Go doesn’t provide automatic support for getters and setters.
-
There’s nothing wrong with providing getters and setters yourself, and it’s often appropriate to do so, but it’s neither idiomatic nor necessary to put
Get
into the getter’s name. -
If you have a field called
owner
(lower case, unexported), the getter method should be calledOwner
(upper case, exported), notGetOwner
. -
A setter function, if needed, will likely be called
SetOwner
. -
Both names read well in practice:
owner := obj.Owner() if owner != user { obj.SetOwner(user) }
Interface names
-
By convention, one-method interfaces are named by the method name plus an
-er
suffix or similar modification to construct an agent noun:Reader
,Writer
,Formatter
,CloseNotifier
etc. -
There are a number of such names and it’s productive to honor them and the function names they capture.
-
Read
,Write
,Close
,Flush
,String
and so on have canonical signatures and meanings. -
To avoid confusion, don’t give your method one of those names unless it has the same signature and meaning.
-
Conversely, if your type implements a method with the same meaning as a method on a well-known type, give it the same name and signature; call your string-converter method
String
notToString
.
MixedCaps
Finally, the convention in Go is to use MixedCaps
or mixedCaps
rather than underscores to write multiword names.
Semicolons
-
Like C, Go’s formal grammar uses semicolons to terminate statements, but unlike in C, those semicolons do not appear in the source.
If the newline comes after a token that could end a statement, insert a semicolon.
-
Idiomatic Go programs have semicolons only in places such as for loop clauses, to separate the initializer, condition, and continuation elements.
-
They are also necessary to separate multiple statements on a line, should you write code that way.
Control structures
-
There is no do or while loop, only a slightly generalized
for
;switch
is more flexible; -
if
andswitch
accept an optional initialization statement like that offor
; -
break
andcontinue
statements take an optional label to identify what to break or continue; -
and there are new control structures including a type switch and a multiway communications multiplexer,
select
. -
There are no parentheses and the bodies must always be brace-delimited.
If
if x > 0 {
return y
}
if f, err: = os.Open(name); err != nil {
return err
}
For
// Like a C for
for init; condition; post { }
// Like a C while
for condition { }
// Like a C for(;;)
for { }
// Like a C do-while
for {
// do something
if condition; {
break
}
}
If you’re looping over an array, slice, string, or map, or reading from a channel, a range
clause can manage the loop.
for key, value := range map {
}
// If you only need the second item in the range (the value),
// use the blank identifier, an underscore, to discard the first:
for _, value := range map {
}
for index, value := range array {
}
for value := range channel {
}
For strings, the range
does more work for you, breaking out individual Unicode code points by parsing the UTF-8. Erroneous encodings consume one byte and produce the replacement rune U+FFFD. (The name (with associated builtin type) rune
is Go terminology for a single Unicode code point.)
for pos, char := range "日本\x80語" { // \x80 is an illegal UTF-8 encoding
fmt.Printf("character %#U starts at byte position %d\n", char, pos)
}
// Output:
// character U+65E5 '日' starts at byte position 0
// character U+672C '本' starts at byte position 3
// character U+FFFD '�' starts at byte position 6
// character U+8A9E '語' starts at byte position 7
Go has no comma operator and ++
and --
are statements not expressions. Thus if you want to run multiple variables in a for you should use parallel assignment (although that precludes ++ and --).
// Reverse a
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
Switch
Go’s switch is more general than C’s.
-
The expressions need not be constants or even integers,
-
the cases are evaluated top to bottom until a match is found,
-
and if the
switch
has no expression it switches ontrue
. -
It’s therefore possible—and idiomatic—to write an
if-else-if-else
chain as aswitch
. -
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 aswitch
early. -
Sometimes, though, it’s necessary to break out of a surrounding loop, not the switch, and in Go that can be accomplished by putting a label on the loop and "breaking" to that label.
-
Of course, the
continue
statement also accepts an optional label but it applies only to loops.
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
Loop:
for n := 0; n < len(src); n += size {
switch {
case src[n] < sizeOne:
if validateOnly {
break
}
size = 1
update(src[n])
case src[n] < sizeTwo:
if n+1 >= len(src) {
err = errShortInput
break Loop
}
if validateOnly {
break
}
size = 2
update(src[n] + src[n+1]<<shift)
}
}
Type switch
A switch can also be used to discover the dynamic type of an interface variable.
-
Such a type switch uses the syntax of a type assertion with the keyword
type
inside the parentheses. -
If the switch declares a variable in the expression, the variable will have the corresponding type in each clause.
-
It’s also idiomatic to reuse the name in such cases, in effect declaring a new variable with the same name but a different type in each case.
var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
fmt.Printf("unexpected type %T\n", t) // %T prints whatever type t has
case bool:
fmt.Printf("boolean %t\n", t) // t has type bool
case int:
fmt.Printf("integer %d\n", t) // t has type int
case *bool:
fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
Select
The select
statement lets a goroutine wait on multiple communication operations.
A select
blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.
The default
case in a select
is run if no other case is ready.
Use a default
case to try a send or receive without blocking:
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
Break, Continue and Goto
A break
statement terminates execution of the innermost for
, switch
, or select
statement within the same function.
A continue
statement begins the next iteration of the innermost for
loop at its post statement within the same function.
A goto
statement transfers control to the statement with the corresponding label within the same function.
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
RawLoop:
for {
select {
case <-tick:
fmt.Println("tick.")
continue RawLoop // just for demo, needless
case <-boom:
fmt.Println("BOOM!")
break RawLoop
default:
fmt.Println(". .")
time.Sleep(50 * time.Millisecond)
goto RawLoop // just for demo, needless
}
}
}
Fallthrough
A fallthrough
statement transfers control to the first statement of the next case clause in an expression switch
statement. It may be used only as the final non-empty statement in such a clause.
func main() {
switch {
case 10 > 11:
fmt.Println("10 > 11")
case 1 < 5:
fallthrough
case 1 > 10:
fmt.Println("1 > 10")
}
}
// Output:
// 1 > 10
Defer
A defer
statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
Functions
Multiple return values
func (file *File) Write(b []byte) (n int, err error)
Named result parameters
-
The return or result "parameters" of a Go function can be given names and used as regular variables, just like the incoming parameters.
-
When named, they are initialized to the zero values for their types when the function begins;
-
if the function executes a return statement with no arguments, the current values of the result parameters are used as the returned values.
Defer
-
Go’s
defer
statement schedules a function call (the deferred function) to be run immediately before the function executing thedefer
returns. -
It’s an unusual but effective way to deal with situations such as resources that must be released regardless of which path a function takes to return.
func ReadFile(filename string) ([]byte, error) { f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() return ReadAll(f) }
-
The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes, not when the call executes.
-
Deferred functions are executed in LIFO order (stacking style).
for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } // Output: // 4 3 2 1 0
// All function values created by this loop "capture" // and share the same variable—an addressable storage location, // not its value at that particular moment. for i := 0; i < 5; i++ { defer func() { fmt.Print(i, " ") }() } // Output: // 5 5 5 5 5
for i := 0; i < 5; i++ { // declares inner i, intialized to outer i i := i defer func() { fmt.Print(i, " ") }() } // Output: // 4 3 2 1 0
Data types
// any is an alias for interface{} and is equivalent to interface{} in all ways.
any
// comparable is an interface that is implemented by all comparable types
// (booleans, numbers, strings, pointers, channels, arrays of comparable types,
// structs whose fields are all comparable types). The comparable interface may
// only be used as a type parameter constraint, not as the type of a variable.
comparable
bool // true false
string
int8 int16 int32 int64
uint8 uint16 uint32 uint64 uintptr
int uint // either 32 or 64 bits
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
// more types
pointers structs array slices maps functions interfaces channels
Strings, bytes, runes and characters
-
Go source code is always UTF-8.
-
A string holds arbitrary bytes.
-
A string literal, absent byte-level escapes, always holds valid UTF-8 sequences.
-
Those sequences represent Unicode code points, called runes.
-
No guarantee is made in Go that characters in strings are normalized.
func main() {
const nihongo = "日本語"
for _, runeValue := range nihongo {
fmt.Printf("%#U ", runeValue)
}
fmt.Println()
for index := 0; index < len(nihongo); index++ {
fmt.Printf("%x ", nihongo[index])
}
fmt.Println()
for index := 0; index < len(nihongo); index++ {
fmt.Printf("%q ", nihongo[index])
}
fmt.Println()
for index := 0; index < len(nihongo); index++ {
fmt.Printf("%+q ", nihongo[index])
}
fmt.Println()
}
// Output:
// U+65E5 '日' U+672C '本' U+8A9E '語'
// e6 97 a5 e6 9c ac e8 aa 9e
// 'æ' '\u0097' '¥' 'æ' '\u009c' '¬' 'è' 'ª' '\u009e'
// '\u00e6' '\u0097' '\u00a5' '\u00e6' '\u009c' '\u00ac' '\u00e8' '\u00aa' '\u009e'
Pointers
// A pointer holds the memory address of a value.
// Unlike C, Go has no pointer arithmetic.
// The type `*T` is a pointer to a `T` value. Its zero value is `nil`.
var p *int
i := 42
// The `&` operator generates a pointer to its operand.
p = &i
// The `*` operator ("dereferencing" or "indirecting") denotes the pointer's underlying value.
*p = 21
Structs
// A struct is a collection of fields.
type Vertex struct {
X, Y int
}
var (
// A struct literal denotes a newly allocated struct value by listing the values of its fields.
v1 = Vertex{1, 2} // has type Vertex
// You can list just a subset of fields by using the Name: syntax.
// (And the order of named fields is irrelevant.)
v2 = Vertex{X: 1} // Y:0 is implicit
v3 = Vertex{} // X:0 and Y:0
// The special prefix & returns a pointer to the struct value
p = &Vertex{1, 2} // has type *Vertex
)
func main() {
// Struct fields are accessed using a dot.
p.X = 1e9
fmt.Println(v1, p, v2, v3)
}
Arrays
-
The type
[n]T
is an array ofn
values of typeT
. -
Arrays are values.
Assigning one array to another copies all the elements.
-
In particular, if you pass an array to a function, it will receive a copy of the array, not a pointer to it.
-
The size of an array is part of its type.
The types
[10]int
and[20]int
are distinct, so arrays cannot be resized.
var a [2]string
a[0] = "Hello"
a[1] = "World"
// an array literal
primes := [6]int{2, 3, 5, 7, 11, 13}
Slices
-
A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array.
-
The type
[]T
is a slice with elements of typeT
. -
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 expressionslen(s)
andcap(s)
. -
You can extend a slice’s length by re-slicing it, provided it has sufficient capacity.
Nil slices
-
The zero value of a slice is
nil
. -
A
nil
slice has a length and capacity of 0 and has no underlying array.
Appending to a slice
-
It is common to append new elements to a slice, and so Go provides a built-in
append
function.func append(s []T, vs ...T) []T
-
The resulting value of
append
is a slice containing all the elements of the original slice plus the provided values. -
If the backing array of
s
is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array.var s []int // append works on nil slices. s = append(s, 0) // The slice grows as needed. s = append(s, 1) // We can add more than one element at a time. s = append(s, 2, 3, 4)
Maps
-
Maps are a convenient and powerful built-in data structure that associate values of one type (the key) with values of another type (the element or value).
-
The key can be of any type for which the equality operator is defined, such as integers, floating point and complex numbers, strings, pointers, interfaces (as long as the dynamic type supports equality), structs and arrays.
-
Slices cannot be used as map keys, because equality is not defined on them.
-
Like slices, maps hold references to an underlying data structure.
If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller.
-
The zero value of a map is
nil
.A
nil
map has no keys, nor can keys be added. -
Map literals are like struct literals, but the keys are required.
var m map[string]int // <nil> m = map[string]int{ "hello": 100, "world": 200, }
-
The
make
function returns a map of the given type with an optional capacity hint as arguments, initialized and ready for use.// m := make(map[string]int, 100) m := make(map[string]int) // insert or update an element m["Answer"] = 42 // delete an element: // The delete function doesn’t return anything, and will do nothing if the specified key doesn’t exist. delete(m, "Answer") // retrieve an element // If the requested key doesn’t exist, we get the value type’s zero value. v := m["Answer"] // test that a key is present with a two-value assignment v, ok := m["Answer"]
Functions
-
Functions are values too.
They can be passed around just like other values.
-
Function values may be used as function arguments and return values.
-
Go functions may be closures.
-
A closure is a function value that references variables from outside its body.
-
The function may access and assign to the referenced variables; in this sense the function is "bound" to the variables.
func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 3; i++ { fmt.Println( pos(i), neg(-2*i), ) } // Output: // 0 0 // 1 -2 // 3 -6 }
-
Methods
-
Go does not have classes.
However, you can define methods on any named type (except a pointer or an interface).
-
A method is a function with a special receiver argument.
The receiver appears in its own argument list between the
func
keyword and the method name.You can only declare a method with a receiver whose type is defined in the same package as the method.
Choosing a value or pointer receiver
-
There are two reasons to use a pointer receiver.
-
The first is so that the method can modify the value that its receiver points to.
-
The second is to avoid copying the value on each method call.
This can be more efficient if the receiver is a large struct, for example.
-
-
In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both.
-
The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.
This rule arises because pointer methods can modify the receiver; invoking them on a value would cause the method to receive a copy of the value, so any modifications would be discarded. The language therefore disallows this mistake.
There is a handy exception, though. When the value is addressable, the language takes care of the common case of invoking a pointer method on a value by inserting the address operator automatically.
package bufio // import "bufio" func (b *Reader) Read(p []byte) (n int, err error) func (b *Writer) Write(p []byte) (nn int, err error)
Nil is a valid receiver value
-
Just as some functions allow nil pointers as arguments, so do some methods for their receiver, especially if nil is a meaningful zero value of the type, as with maps and slices.
-
When you define a type whose methods allow nil as a receiver value, it’s worth pointing this out explicitly in its documentation comment.
Interfaces
An interface type defines a type set. A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil
.
An interface type is specified by a list of interface elements. An interface element is either a method or a type element, where a type element is a union of one or more type terms. A type term is either a single type or a single underlying type.
Interfaces in Go provide a way to specify the behavior of an object: if something can do this, then it can be used here.
Interfaces are implemented implicitly
-
A type implements an interface by implementing its methods.
There is no explicit declaration of intent, no "implements" keyword.
-
Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement.
Interface values
-
Under the hood, interface values can be thought of as a tuple of a value and a concrete type:
(value, type)
-
An interface value holds a value of a specific underlying concrete type.
-
Calling a method on an interface value executes the method of the same name on its underlying type.
Interface values with nil underlying values
-
If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
-
In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver.
-
Note that an interface value that holds a nil concrete value is itself non-nil.
type I interface { M() } type T struct{} func (t *T) M() { if t == nil { fmt.Println("<nil>") return } } func main() { var i I var t *T i = t i.M() fmt.Printf("(%v, %T)\n", i, i) i = &T{} i.M() fmt.Printf("(%v, %T)\n", i, i) // Output: // <nil> // (<nil>, *main.T) // (&{}, *main.T) }
Nil interface values
-
A nil interface value holds neither value nor concrete type.
-
Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.
var i I fmt.Printf("(%v, %T)\n", i, i) i.M() // (<nil>, <nil>) // panic: runtime error: invalid memory address or nil pointer dereference
The empty interface
-
The interface type that specifies zero methods is known as the empty interface:
interface{}
-
An empty interface may hold values of any type. (Every type implements at least zero methods.)
-
Empty interfaces are used by code that handles values of unknown type.
-
For convenience, the predeclared type
any
is an alias for the empty interface.$ go doc builtin.any package builtin // import "builtin" type any = interface{} any is an alias for interface{} and is equivalent to interface{} in all ways. func recover() any
General interfaces
-
In their most general form, an interface element may also be an arbitrary type term
T
, or a term of the form~T
specifying the underlying typeT
, or a union of termst1|t2|…|tn
. -
By construction, an interface’s type set never contains an interface type.
// An interface representing only the type int. type I0 interface { int } // An interface representing all types with underlying type int. type I1 interface { ~int } // An interface representing all types with underlying type int that implement the String method. type I2 interface { ~int String() string } // An interface representing an empty type set: there is no type that is both an int and a string. type I3 interface { int string }
-
In a term of the form
~T
, the underlying type ofT
must be itself, andT
cannot be an interface.type MyInt int type MyI interface { ~[]byte // the underlying type of []byte is itself ~MyInt // illegal: the underlying type of MyInt is not MyInt ~error // illegal: error is an interface }
-
Union elements denote unions of type sets:
// The Float interface represents all floating-point types // (including any named types whose underlying types are // either float32 or float64). type Float interface { ~float32 | ~float64 }
Generality
-
If a type exists only to implement an interface and will never have exported methods beyond that interface, there is no need to export the type itself.
-
Exporting just the interface makes it clear the value has no interesting behavior beyond what is described in the interface.
-
It also avoids the need to repeat the documentation on every instance of a common method.
-
In such cases, the constructor should return an interface value rather than the implementing type.
Interface conversions and type assertions
-
A type assertion provides access to an interface value’s underlying concrete value.
t := i.(T)
This statement asserts that the interface value
i
holds the concrete typeT
and assigns the underlyingT
value to the variablet
.If
i
does not hold aT
, 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 aT
, thent
will be the underlying value andok
will betrue
.If not,
ok
will befalse
andt
will be the zero value of typeT
, 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 typeT
is replaced with the keywordtype
.switch v := i.(type) { case T: // here v has type T case S: // here v has type S default: // no match; here v has the same type as i }
Embedding: interfaces and structs
-
Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.
package io // import "io" type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } // ReadWriter is the interface that combines the Reader and Writer interfaces. type ReadWriter interface { Reader Writer }
package bufio // import "bufio" type Reader struct { // Has unexported fields. } func (b *Reader) Read(p []byte) (n int, err error) type Writer struct { // Has unexported fields. } func (b *Writer) Write(p []byte) (nn int, err error) // ReadWriter stores pointers to a Reader and a Writer. // It implements io.ReadWriter. type ReadWriter struct { *Reader *Writer }
-
There’s an important way in which embedding differs from subclassing.
-
When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one.
For example, when the
Read
method of abufio.ReadWriter
is invoked, the receiver is thereader
field of theReadWriter
, not theReadWriter
itself.type Reader struct { } func (r *Reader) Read() { fmt.Println("Read") } type Writer struct { } func (r *Writer) Write() { fmt.Println("Write") } type ReadWriter struct { *Reader *Writer } func main() { rw := ReadWriter{} rw.Read() // same as rw.Reader.Read() rw.Reader.Read() // Output: // Read // Read }
-
-
Embedding types introduces the problem of name conflicts but the rules to resolve them are simple.
-
First, a field or method
X
hides any other itemX
in a more deeply nested part of the type. -
Second, if the same name appears at the same nesting level, it is usually an error.
However, if the duplicate name is never mentioned in the program outside the type definition, it is OK.
This qualification provides some protection against changes made to types embedded from outside; there is no problem if a field is added that conflicts with another field in another subtype if neither field is ever used.
-
Channels
-
Channels are a typed conduit through which you can send and receive values with the channel operator,
<-
.ch <- v // Send v to channel ch. v := <-ch // Receive from ch, and assign value to v. // (The data flows in the direction of the arrow.)
-
Like maps and slices, channels must be created before use:
// By default, sends and receives block until the other side is ready. // This allows goroutines to synchronize without explicit locks or condition variables. blockChan := make(chan int) // Sends to a buffered channel block only when the buffer is full. // Receives block when the buffer is empty. bufChan := make(chan int, 100)
-
A sender can
close
a channel to indicate that no more values will be sent.-
After calling close, and after any previously sent values have been received, receive operations will return the zero value for the channel’s type without blocking.
-
Note that it is only necessary to close a channel if the receiver is looking for a close. Closing the channel is a control signal on the channel indicating that no more data follows.
-
The multi-valued assignment form of the receive operator reports whether a received value was sent before the channel was closed.
// ok is false if there are no more values to receive and the channel is closed. v, ok := <-ch
-
The loop for
v := range c
receives values from the channel repeatedly until it is closed. -
Attempting to close an already-closed channel causes a panic, as does closing a nil channel.
-
Sending to a closed channel causes a run-time panic.
-
Note: Only the sender should close a channel, never the receiver.
Sending on a closed channel will cause a panic.
-
Another note: Channels aren’t like files; you don’t usually need to close them.
Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a
range
loop.
-
-
A channel may be constrained only to send or only to receive by assignment or explicit conversion.
func main() { var ( _ = make(chan int) // bidirectional _ = make(<-chan int) // receive-only _ = make(chan<- int) // send-only ) ch := make(chan int) // send-only go func(ch chan<- int) { for i := 0; i < 3; i++ { ch <- i } close(ch) }(ch) // receive-only go func(ch <-chan int) { for v := range ch { fmt.Println(v) } }(ch) time.Sleep(time.Millisecond) // Output: // 0 // 1 // 2 }
func main() { ch1 := make(chan int) ch2 := make(chan int, 2) // buffering channel quit := make(chan int) go func() { for i := 1; ; i++ { ch1 <- 2 * i time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) } }() go func(ch chan<- int) { for i := 1; ; i++ { ch <- 2*i + 1 time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond) } }(ch2) go func() { <-time.After(time.Duration(5e3) * time.Millisecond) quit <- 0 }() // The select statement lets a goroutine wait on multiple communication operations. // A select blocks until one of its cases can run, then it executes that case. // It chooses one at random if multiple are ready. ch3 := make(chan int) timeout := time.After(500 * time.Millisecond) go func() { defer close(ch3) for { // multiplexing: ch1 + ch2 => ch3 select { case ch3 <- <-ch1: case ch3 <- <-ch2: case <-timeout: fmt.Println("You're too slow.") return case <-quit: fmt.Println("Quit.") return } } }() for v := range ch3 { fmt.Println(v) } }
Type conversions
The expression T(v)
converts the value v
to the type T
.
// Some numeric conversions:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// Or, put more simply:
i := 42
f := float64(i)
u := uint(f)
Type parameters and Generics
Go functions can be written to work on multiple types using type parameters. The type parameters of a function appear between brackets, before the function’s arguments.
func Index[T comparable](s []T, x T) int
This declaration means that s
is a slice of any type T
that fulfills the built-in constraint comparable
. x
is also a value of the same type.
comparable
is a useful constraint that makes it possible to use the ==
and !=
operators on values of the type.
func main() {
si := []int{10, 20, 15, -10}
fmt.Println(Index(si, 15))
ss := []string{"foo", "bar", "baz"}
fmt.Println(Index(ss, "buz"))
}
// Output:
// 2
// -1
// Index returns the index of x in s, or -1 if not found.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
// v and x are type T, which has the comparable
// constraint, so we can use == here.
if x == v {
return i
}
}
return -1
}
In addition to generic functions, Go also supports generic types. A type can be parameterized with a type parameter, which could be useful for implementing generic data structures.
package main
import (
"fmt"
"strings"
"golang.org/x/exp/constraints"
)
type ComparableOrdered interface {
comparable
constraints.Ordered
}
// List represents a singly-linked list that holds
// values of `ComparableOrdered` type.
type List[T ComparableOrdered] struct {
next *List[T]
val T
}
func (head *List[T]) append(vals ...T) {
var a = func(val T) {
tail := head
for tail.next != nil {
tail = tail.next
}
tail.next = &List[T]{val: val}
}
for _, val := range vals {
a(val)
}
}
func (head *List[T]) max() T {
max := head.val
node := head.next
for node != nil {
if node.val > max {
max = node.val
}
node = node.next
}
return max
}
func (head *List[T]) String() string {
var b strings.Builder
node := head
for node != nil {
fmt.Fprintf(&b, "%v", node.val)
node = node.next
if node != nil {
fmt.Fprint(&b, " -> ")
}
}
return b.String()
}
func main() {
list := &List[int]{val: 20}
list.append(10, 30, 60)
list.append(40)
fmt.Printf("list: %v\n", list)
fmt.Printf("max: %v", list.max())
// Output:
// list: 20 -> 60 -> 30 -> 10 -> 40
// max: 60
}
fmt.Printf
Package fmt
implements formatted I/O with functions analogous to C’s printf and scanf. The format 'verbs' are derived from C’s but are simpler.
-
The Printing verbs
General:%v the value in a default format when printing structs, the plus flag (%+v) adds field names %#v a Go-syntax representation of the value %T a Go-syntax representation of the type of the value %% a literal percent sign; consumes no value
Boolean:%t the word true or false
Integer:%b base 2 %c the character represented by the corresponding Unicode code point %d base 10 %o base 8 %O base 8 with 0o prefix %q a single-quoted character literal safely escaped with Go syntax. %x base 16, with lower-case letters for a-f %X base 16, with upper-case letters for A-F %U Unicode format: U+1234; same as "U+%04X"
Floating-point and complex constituents:%b decimalless scientific notation with exponent a power of two, in the manner of strconv.FormatFloat with the 'b' format, e.g. -123456p-78 %e scientific notation, e.g. -1.234456e+78 %E scientific notation, e.g. -1.234456E+78 %f decimal point but no exponent, e.g. 123.456 %F synonym for %f %g %e for large exponents, %f otherwise. Precision is discussed below. %G %E for large exponents, %F otherwise %x hexadecimal notation (with decimal power of two exponent), e.g. -0x1.23abcp+20 %X upper-case hexadecimal notation, e.g. -0X1.23ABCP+20
String and slice of bytes (treated equivalently with these verbs):%s the uninterpreted bytes of the string or slice %q a double-quoted string safely escaped with Go syntax %x base 16, lower-case, two characters per byte %X base 16, upper-case, two characters per byte
Slice:%p address of 0th element in base 16 notation, with leading 0x
Pointer:%p base 16 notation, with leading 0x The %b, %d, %o, %x and %X verbs also work with pointers, formatting the value exactly as if it were an integer.
The default format for %v is:bool: %t int, int8 etc.: %d uint, uint8 etc.: %d, %#x if printed with %#v float32, complex64, etc: %g string: %s chan: %p pointer: %p
For compound objects, the elements are printed using these rules, recursively, laid out like this:struct: {field0 field1 ...} array, slice: [elem0 elem1 ...] maps: map[key1:value1 key2:value2 ...] pointer to above: &{}, &[], &map[]
Other flags:'+' always print a sign for numeric values; guarantee ASCII-only output for %q (%+q) '-' pad with spaces on the right rather than the left (left-justify the field) '#' alternate format: add leading 0b for binary (%#b), 0 for octal (%#o), 0x or 0X for hex (%#x or %#X); suppress 0x for %p (%#p); for %q, print a raw (backquoted) string if strconv.CanBackquote returns true; always print a decimal point for %e, %E, %f, %F, %g and %G; do not remove trailing zeros for %g and %G; write e.g. U+0078 'x' if the character is printable for %U (%#U). ' ' (space) leave a space for elided sign in numbers (% d); put spaces between bytes printing strings or slices in hex (% x, % X) '0' pad with leading zeros rather than spaces; for numbers, this moves the padding after the sign; ignored for strings, byte slices and byte arrays
-
Width and Precision
-
Width is specified by an optional decimal number immediately preceding the verb.
If absent, the width is whatever is necessary to represent the value.
-
Precision is specified after the (optional) width by a period followed by a decimal number.
If no period is present, a default precision is used. A period with no following number specifies a precision of zero.
%f default width, default precision %9f width 9, default precision %.2f default width, precision 2 %9.2f width 9, precision 2 %9.f width 9, precision 0
-
-
type Stringer
type Stringer interface { String() string }
Stringer
is implemented by any value that has aString
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 asPrint
.// Animal has a Name and an Age to represent an animal. type Animal struct { Name string Age uint } // String makes Animal satisfy the Stringer interface. func (a Animal) String() string { return fmt.Sprintf("%v (%d)", a.Name, a.Age) } func main() { a := Animal{ Name: "Gopher", Age: 2, } fmt.Println(a) // Output: // Gopher (2) }
Initialization
Constants
-
Constants are declared like variables, but with the
const
keyword. -
Constants cannot be declared using the
:=
syntax. -
Constants are created at compile time, even when defined as locals in functions, and can only be numbers, characters (runes), strings or booleans.
-
Because of the compile-time restriction, the expressions that define them must be constant expressions, evaluatable by the compiler.
-
In Go, enumerated constants are created using the
iota
enumerator.type Weekday int const ( Sunday Weekday = iota + 1 // iota: 0 ~ Sunday : 1 _ // iota: 1 ~ iota increased // comments // iota: 1 ~ skip: comment // iota: 1 ~ skip: empty line Monday // iota: 2 ~ Monday : 3 Tuesday // iota: 3 ~ Monday : 4 Wednesday // iota: 4 ~ Monday : 5 Thursday // iota: 5 ~ Monday : 6 Friday // iota: 6 ~ Monday : 7 Saturday // iota: 7 ~ Monday : 8 )
iota (noun) /aɪˈəʊtə/ /aɪˈəʊtə/ 1. [singular] (usually used in negative sentences) an extremely small amount There is not one iota of truth (= no truth at all) in the story. I don't think that would help one iota. 2. the 9th letter of the Greek alphabet (I, ι) ref: https://www.oxfordlearnersdictionaries.com/us/definition/english/iota
Allocation with new and make
-
Go has two allocation primitives, the built-in functions
new
andmake
.They do different things and apply to different types, which can be confusing, but the rules are simple.
-
new
is a built-in function that allocates memory, but unlike its namesakes in some other languages it does not initialize the memory, it only zeros it.That is,
new(T)
allocates zeroed storage for a new item of typeT
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 fromnew(T)
.It creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type
T
(not*T
).The reason for the distinction is that these three types represent, under the covers, references to data structures that must be initialized before use.
var p *[]int = new([]int) // allocates slice structure; *p == nil; rarely useful var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints // Unnecessarily complex: var p *[]int = new([]int) *p = make([]int, 100, 100) // Idiomatic: v := make([]int, 100)
The init function
-
Each source file can define its own niladic
init
function to set up whatever state is required. -
Actually each file can have multiple init functions.
-
init
is called after all the variable declarations in the package have evaluated their initializers, and those are evaluated only after all the imported packages have been initialized.
package hello
import (
"fmt"
)
func init() {
fmt.Print("hello ")
}
package world
import (
"fmt"
_ "hello"
)
func init() {
fmt.Print("world")
}
package main
import (
"fmt"
_ "world"
)
const mark = "!"
func init() {
fmt.Print(mark)
}
func main() {
// Output:
// hello world!
}
Zero values
Variables declared without an explicit initial value are given their zero value.
The zero value is:
-
0
for numeric types, -
false
for the boolean type, -
""
(the empty string) for strings, -
nil
for the pointers, slices, maps, functions, interfaces, channels,
Concurrency
Race Conditions
-
A race condition is a situation in which the program does not give the correct result for some interleaving of the operations of multiple goroutines.
-
A data race, that is, a particular kind of race condition, occurs whenever two goroutines access the same variable concurrently and at least one of the accesses is a write.
It follows from this definition that there are three ways to avoid a data race.
-
The first way is not to write the variable.
-
The second way (channels: share memory by communication) to avoid a data race is to avoid accessing the variable from multiple goroutines.
-
The third way (mutual exclusion:
sync.Mutex
,sync.RWMutex
) to avoid a data race is to allow many goroutines to access the variable, but only one at a time.
-
-
Synchronization is about more than just the order of execution of multiple goroutines; synchronization also affets memory.
Race Detector
-
The race detector (just add the
-race
flag to yourgo build
,go run
, orgo test
command) studies this steam of events, looking for cases in which one goroutine reads or writes a shared variables that was most recently written by a different goroutine without an intervening synchronization operation. -
The race detector reports all data races that wre actually executed. However, it can only detect race conditions that occur during a run; it cannot prove that none will ever occur.
func main() { var wg sync.WaitGroup var x, y int wg.Add(1) go func() { defer wg.Done() x = 1 fmt.Printf("y = %d\n", y) }() wg.Add(1) go func() { defer wg.Done() y = 1 fmt.Printf("x = %d\n", x) }() wg.Wait() }
$ go run -race race.go
x = 0 ================== WARNING: DATA RACE Write at 0x00c0000a6020 by goroutine 7: main.main.func1() /tmp/race.go:16 +0x8a Previous read at 0x00c0000a6020 by goroutine 8: main.main.func2() /tmp/race.go:24 +0xaa Goroutine 7 (running) created at: main.main() /tmp/race.go:14 +0x119 Goroutine 8 (finished) created at: main.main() /tmp/race.go:21 +0x166 ================== ================== WARNING: DATA RACE Read at 0x00c0000a6028 by goroutine 7: main.main.func1() /tmp/race.go:17 +0xaa Previous write at 0x00c0000a6028 by goroutine 8: main.main.func2() /tmp/race.go:23 +0x8a Goroutine 7 (running) created at: main.main() /tmp/race.go:14 +0x119 Goroutine 8 (finished) created at: main.main() /tmp/race.go:21 +0x166 ================== y = 1 Found 2 data race(s) exit status 66
Happen before
-
Within a single goroutine, reads and writes must behave as if they executed in the order specified by the program.
-
That is, compilers and processors may reorder the reads and writes executed within a single goroutine only when the reordering does not change the behavior within that goroutine as defined by the language specification.
-
Because of this reordering, the execution order observed by one goroutine may differ from the order perceived by another.
For example, if one goroutine executes
a = 1; b = 2
;, another might observe the updated value ofb
before the updated value ofa
. -
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
andsync/atomic
packages.
Share by communicating
-
Do not communicate by sharing memory; instead, share memory by communicating.
-
Go encourages a different approach in which shared values are passed around on channels and, in fact, never actively shared by separate threads of execution.
-
Only one goroutine has access to the value at any given time. Data races cannot occur, by design.
-
-
One way to think about this model is to consider a typical single-threaded program running on one CPU.
-
It has no need for synchronization primitives.
-
Now run another such instance; it too needs no synchronization.
-
Now let those two communicate; if the communication is the synchronizer, there’s still no need for other synchronization.
-
Unix pipelines, for example, fit this model perfectly.
-
Although Go’s approach to concurrency originates in Hoare’s Communicating Sequential Processes (CSP), it can also be seen as a type-safe generalization of Unix pipes.
-
Goroutines
-
A goroutine has a simple model: it is a function executing concurrently with other goroutines in the same address space.
-
It is lightweight, costing little more than the allocation of stack space.
-
And the stacks start small, so they are cheap, and grow by allocating (and freeing) heap storage as required.
-
-
Goroutines are multiplexed onto multiple OS threads so if one should block, such as while waiting for I/O, others continue to run.
-
Their design hides many of the complexities of thread creation and management.
-
Prefix a function or method call with the
go
keyword to run the call in a new goroutine. When the call completes, the goroutine exits, silently. -
The evaluation of
f
,x
,y
, andz
ofgo f(x, y, z)
happens in the current goroutine and the execution off
happens in the new goroutine.package main import ( "fmt" "time" ) func main() { // All function values created by this loop “capture” // and share the same variable—an addressable storage location, // not its value at that particular moment. for i := 0; i < 5; i++ { go func() { fmt.Print(i, " ") }() } time.Sleep(time.Millisecond) fmt.Println() for i := 0; i < 5; i++ { i := i go func() { fmt.Print(i, " ") }() } time.Sleep(time.Millisecond) // Output: // 5 5 5 5 5 // 4 0 1 2 3 // ignore the order }
-
Channels
-
Like maps, channels are allocated with
make
, and the resulting value acts as a reference to an underlying data structure.-
If an optional integer parameter is provided, it sets the buffer size for the channel.
-
The default is zero, for an unbuffered or synchronous channel.
ci := make(chan int) // unbuffered channel of integers cj := make(chan int, 0) // unbuffered channel of integers cs := make(chan *os.File, 100) // buffered channel of pointers to Files
-
-
Receivers always block until there is data to receive.
-
The sender blocks only until the value has been copied to the buffer;
-
A buffered channel can be used like a semaphore, for instance to limit throughput.
-
The assembly line metaphor (pipeline) is useful one for channels and goroutines.
// A concurrent prime sieve package main // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func Filter(in <-chan int, out chan<- int, prime int) { for { i := <-in // Receive value from 'in'. if i%prime != 0 { out <- i // Send 'i' to 'out'. } } } // The prime sieve: Daisy-chain Filter processes. func main() { ch := make(chan int) // Create a new channel. go Generate(ch) // Launch Generate goroutine. for i := 0; i < 10; i++ { prime := <-ch print(prime, "\n") ch1 := make(chan int) go Filter(ch, ch1, prime) ch = ch1 } }
Parallelization
-
Be sure not to confuse the ideas of concurrency—structuring a program as independently executing components—and parallelism—executing calculations in parallel for efficiency on multiple CPUs.
-
Although the concurrency features of Go can make some problems easy to structure as parallel computations, Go is a concurrent language, not a parallel one, and not all parallelization problems fit Go’s model.
package runtime // import "runtime" func NumCPU() int NumCPU returns the number of logical CPUs usable by the current process. The set of available CPUs is checked by querying the operating system at process startup. Changes to operating system CPU allocation after process startup are not reflected. func GOMAXPROCS(n int) int GOMAXPROCS sets the maximum number of CPUs that can be executing simultaneously and returns the previous setting. If n < 1, it does not change the current setting. The number of logical CPUs on the local machine can be queried with NumCPU. This call will go away when the scheduler improves.
Errors
-
Library routines must often return some sort of error indication to the caller.
-
Go’s multivalue return makes it easy to return a detailed error description alongside the normal return value.
-
It is good style to use this feature to provide detailed error information.
-
By convention, errors have type
error
, a simple built-in interface.type error interface { Error() string }
-
The simplest way to create an
error
is by callingerrors.New
, which return a newerror
for a given error message. -
Calls to
errors.New
are relatively infrequent because there’s a conveninent wrapper function,fmt.Errorf
, that does string formatting too. -
When feasible, error strings should identify their origin, such as by having a prefix naming the operation or package that generated the error.
For example, in
package image
, the string representation for a decoding error due to an unknown format is "image: unknown format". -
Callers that care about the precise error details can use a type switch or a type assertion to look for specific errors and extract details.
Panic
-
There is a built-in function
panic
that in effect creates a runtime unrecoverable error that will stop the program. -
The function takes a single argument of arbitrary type—often a string—to be printed as the program dies.
package builtin // import "builtin" func panic(v interface{}) The panic built-in function stops normal execution of the current goroutine. When a function F calls panic, normal execution of F stops immediately. Any functions whose execution was deferred by F are run in the usual way, and then F returns to its caller. To the caller G, the invocation of F then behaves like a call to panic, terminating G's execution and running any deferred functions. This continues until all functions in the executing goroutine have stopped, in reverse order. At that point, the program is terminated with a non-zero exit code. This termination sequence is called panicking and can be controlled by the built-in function recover.
Recover
-
When
panic
is called, including implicitly for runtime errors such as indexing a slice out of bounds or failing a type assertion,-
it immediately stops execution of the current function
-
and begins unwinding the stack of the goroutine,
-
running any deferred functions along the way.
-
If that unwinding reaches the top of the goroutine’s stack, the program dies.
-
-
However, it is possible to use the built-in function
recover
to regain control of the goroutine and resume normal execution. -
A call to
recover
stops the unwinding and returns the argument passed to panic.-
Because the only code that runs while unwinding is inside deferred functions, recover is only useful inside deferred functions.
func F() { panic("F: panic.") } func G() { defer func() { e := recover() if e != nil { fmt.Println("G: recover:", e) } }() F() } func main() { G() // Output: // G: recover: F: panic. }
-
Testing
-
The
go test
subcommand is a test driver for Go packages that are organized according to certain conventions. -
In a package directory, files whose names end with
_test.go
are not part of the package ordinarily built bygo build
but are a part of it when built bygo test
. -
Within *_test.go files, four kinds of functions are treated specially: tests, fuzzs, benchmarks, and examples.
-
A test function, which is a function whose name begins with Test, exercises some program logic for correct behavior;
go test
calls the test function and report the result, which is either PASS or FAIL. -
With fuzzing, random data is run against your test in an attempt to find vulnerabilities or crash-causing inputs.
-
A benchmark function has a name beginning with Benchmark and measures the performance of some operation;
go test
reports the mean execution time of the operation. -
And an example function, whose name starts with Example, provides machine-checked documentation.
func Foo(s string) string { return s } func TestFoo(t *testing.T) { var tests = []struct { s string want string }{ {"Hello", "Hello"}, {"世界!", "世界!"}, } for _, test := range tests { if got := Foo(test.s); got != test.want { t.Errorf("foo(%q) == %q, want %q", test.s, got, test.want) } } } // Fuzz test func FuzzFoo(f *testing.F) { // Seed corpus addition f.Add("hello") // Fuzz target f.Fuzz(func(t *testing.T, s string) { // s string // Fuzzing arguments if got := Foo(s); got != s { t.Errorf("foo(%q) == %q, want %q", s, got, s) } }) } func BenchmarkFoo(b *testing.B) { for n := 0; n < b.N; n++ { } } func ExampleFoo() { fmt.Println("BAR") // Output: // BAR }
$ GO111MODULE=off go test PASS ok _/tmp/learn-notes 0.003s $ GO111MODULE=off go test -fuzz=Fuzz -fuzztime=3s fuzz: elapsed: 0s, gathering baseline coverage: 0/1 completed fuzz: elapsed: 0s, gathering baseline coverage: 1/1 completed, now fuzzing with 4 workers fuzz: elapsed: 3s, execs: 226192 (75387/sec), new interesting: 0 (total: 1) fuzz: elapsed: 3s, execs: 226192 (0/sec), new interesting: 0 (total: 1) PASS ok _/tmp/learn-notes 3.127s $ GO111MODULE=off go test -bench=.* goos: linux goarch: amd64 cpu: Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz BenchmarkFoo-4 1000000000 0.5349 ns/op PASS ok _/tmp/learn-notes 0.605s
-
Modules
// In Go, if an old package and a new package have the same import path,
// the new package must be backwards compatible with the old package.
// There is certainly a cost to needing to introduce a new name for each backwards-incompatible API change,
// but as the semver FAQ says, that cost should encourage authors to more clearly consider
// the impact of such changes and whether they are truly necessary.
-
A module is a collection of related Go packages that are versioned together as a single unit.
-
Modules record precise dependency requirements and create reproducible builds.
-
Most often, a version control repository contains exactly one module defined in the repository root.
-
Summarizing the relationship between repositories, modules, and packages:
-
A repository contains one or more Go modules.
-
Each module contains one or more Go packages.
-
Each package consists of one or more Go source files in a single directory.
-
-
Modules must be semantically versioned according to semver, usually in the form
v(major).(minor).(patch)
, such asv0.1.0
,v1.2.3
, orv1.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 themodule
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
References
-
https://stackoverflow.com/questions/24790175/when-is-the-init-function-run
-
https://groups.google.com/g/golang-nuts/c/pZwdYRGxCIk/m/qpbHxRRPJdUJ
-
https://medium.com/@adiach3nko/package-management-with-go-modules-the-pragmatic-guide-c831b4eaaf31
-
Practical Go: Real world advice for writing maintainable Go programs