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