Hello World
Our first program will print the classic "hello world" message. Here's the full source code. |
package main
|
import "fmt"
|
|
func main() {
fmt.Println("hello world")
}
|
To run the program, put the code in |
$ go run hello-world.go
hello world
|
Sometimes we'll want to build our programs into
binaries. We can do this using |
$ go build hello-world.go
$ ls
hello-world hello-world.go
|
We can then execute the built binary directly. |
$ ./hello-world
hello world
|
Now that we can run and build basic Go programs, let's learn more about the language. |
Go has various value types including strings, integers, floats, booleans, etc. Here are a few basic examples. |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
Strings, which can be added together with |
fmt.Println("go" + "lang")
|
Integers and floats. |
fmt.Println("1+1 =", 1+1)
fmt.Println("7.0/3.0 =", 7.0/3.0)
|
Booleans, with boolean operators as you'd expect. |
fmt.Println(true && false)
fmt.Println(true || false)
fmt.Println(!true)
}
|
$ go run values.go
golang
1+1 = 2
7.0/3.0 = 2.3333333333333335
false
true
false
|
In Go, variables are explicitly declared and used by the compiler to e.g. check type-correctness of function calls. |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
|
var a = "initial"
fmt.Println(a)
|
You can declare multiple variables at once. |
var b, c int = 1, 2
fmt.Println(b, c)
|
Go will infer the type of initialized variables. |
var d = true
fmt.Println(d)
|
Variables declared without a corresponding
initialization are zero-valued. For example, the
zero value for an |
var e int
fmt.Println(e)
|
The |
f := "apple"
fmt.Println(f)
}
|
$ go run variables.go
initial
1 2
true
0
apple
|
Go supports constants of character, string, boolean, and numeric values. |
|
package main
|
|
import (
"fmt"
"math"
)
|
|
|
const s string = "constant"
|
func main() {
fmt.Println(s)
|
|
A |
const n = 500000000
|
Constant expressions perform arithmetic with arbitrary precision. |
const d = 3e20 / n
fmt.Println(d)
|
A numeric constant has no type until it's given one, such as by an explicit conversion. |
fmt.Println(int64(d))
|
A number can be given a type by using it in a
context that requires one, such as a variable
assignment or function call. For example, here
|
fmt.Println(math.Sin(n))
}
|
$ go run constant.go
constant
6e+11
600000000000
-0.28470407323754404
|
|
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
The most basic type, with a single condition. |
i := 1
for i <= 3 {
fmt.Println(i)
i = i + 1
}
|
A classic initial/condition/after |
for j := 7; j <= 9; j++ {
fmt.Println(j)
}
|
|
for {
fmt.Println("loop")
break
}
|
You can also |
for n := 0; n <= 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
}
|
$ go run for.go
1
2
3
7
8
9
loop
1
3
5
|
|
We'll see some other |
Branching with |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
Here's a basic example. |
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
|
You can have an |
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
|
A statement can precede conditionals; any variables declared in this statement are available in all branches. |
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
|
Note that you don't need parentheses around conditions in Go, but that the braces are required. |
$ go run if-else.go
7 is odd
8 is divisible by 4
9 has 1 digit
|
|
There is no ternary if
in Go, so you'll need to use a full |
Switch statements express conditionals across many branches. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func main() {
|
|
Here's a basic |
i := 2
fmt.Print("Write ", i, " as ")
switch i {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
}
|
You can use commas to separate multiple expressions
in the same |
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
fmt.Println("It's the weekend")
default:
fmt.Println("It's a weekday")
}
|
|
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
|
A type |
whatAmI := func(i interface{}) {
switch t := i.(type) {
case bool:
fmt.Println("I'm a bool")
case int:
fmt.Println("I'm an int")
default:
fmt.Printf("Don't know type %T\n", t)
}
}
whatAmI(true)
whatAmI(1)
whatAmI("hey")
}
|
$ go run switch.go
Write 2 as two
It's a weekday
It's after noon
I'm a bool
I'm an int
Don't know type string
|
In Go, an array is a numbered sequence of elements of a specific length. |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
Here we create an array |
var a [5]int
fmt.Println("emp:", a)
|
We can set a value at an index using the
|
a[4] = 100
fmt.Println("set:", a)
fmt.Println("get:", a[4])
|
The builtin |
fmt.Println("len:", len(a))
|
Use this syntax to declare and initialize an array in one line. |
b := [5]int{1, 2, 3, 4, 5}
fmt.Println("dcl:", b)
|
Array types are one-dimensional, but you can compose types to build multi-dimensional data structures. |
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
|
Note that arrays appear in the form |
$ go run arrays.go
emp: [0 0 0 0 0]
set: [0 0 0 0 100]
get: 100
len: 5
dcl: [1 2 3 4 5]
2d: [[0 1 2] [1 2 3]]
|
You'll see slices much more often than arrays in typical Go. We'll look at slices next. |
Slices are a key data type in Go, giving a more powerful interface to sequences than arrays. |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
Unlike arrays, slices are typed only by the
elements they contain (not the number of elements).
To create an empty slice with non-zero length, use
the builtin |
s := make([]string, 3)
fmt.Println("emp:", s)
|
We can set and get just like with arrays. |
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("set:", s)
fmt.Println("get:", s[2])
|
|
fmt.Println("len:", len(s))
|
In addition to these basic operations, slices
support several more that make them richer than
arrays. One is the builtin |
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println("apd:", s)
|
Slices can also be |
c := make([]string, len(s))
copy(c, s)
fmt.Println("cpy:", c)
|
Slices support a "slice" operator with the syntax
|
l := s[2:5]
fmt.Println("sl1:", l)
|
This slices up to (but excluding) |
l = s[:5]
fmt.Println("sl2:", l)
|
And this slices up from (and including) |
l = s[2:]
fmt.Println("sl3:", l)
|
We can declare and initialize a variable for slice in a single line as well. |
t := []string{"g", "h", "i"}
fmt.Println("dcl:", t)
|
Slices can be composed into multi-dimensional data structures. The length of the inner slices can vary, unlike with multi-dimensional arrays. |
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
innerLen := i + 1
twoD[i] = make([]int, innerLen)
for j := 0; j < innerLen; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
|
Note that while slices are different types than arrays,
they are rendered similarly by |
$ go run slices.go
emp: [ ]
set: [a b c]
get: c
len: 3
apd: [a b c d e f]
cpy: [a b c d e f]
sl1: [c d e]
sl2: [a b c d e]
sl3: [c d e f]
dcl: [g h i]
2d: [[0] [1 2] [2 3 4]]
|
Check out this great blog post by the Go team for more details on the design and implementation of slices in Go. |
|
Now that we've seen arrays and slices we'll look at Go's other key builtin data structure: maps. |
Maps are Go's built-in associative data type (sometimes called hashes or dicts in other languages). |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
To create an empty map, use the builtin |
m := make(map[string]int)
|
Set key/value pairs using typical |
m["k1"] = 7
m["k2"] = 13
|
Printing a map with e.g. |
fmt.Println("map:", m)
|
Get a value for a key with |
v1 := m["k1"]
fmt.Println("v1: ", v1)
|
The builtin |
fmt.Println("len:", len(m))
|
The builtin |
delete(m, "k2")
fmt.Println("map:", m)
|
The optional second return value when getting a
value from a map indicates if the key was present
in the map. This can be used to disambiguate
between missing keys and keys with zero values
like |
_, prs := m["k2"]
fmt.Println("prs:", prs)
|
You can also declare and initialize a new map in the same line with this syntax. |
n := map[string]int{"foo": 1, "bar": 2}
fmt.Println("map:", n)
}
|
Note that maps appear in the form |
$ go run maps.go
map: map[k1:7 k2:13]
v1: 7
len: 2
map: map[k1:7]
prs: false
map: map[bar:2 foo:1]
|
range iterates over elements in a variety of data
structures. Let's see how to use |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
Here we use |
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
|
|
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
|
|
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
|
|
for k := range kvs {
fmt.Println("key:", k)
}
|
|
for i, c := range "go" {
fmt.Println(i, c)
}
}
|
$ go run range.go
sum: 9
index: 1
a -> apple
b -> banana
key: a
key: b
0 103
1 111
|
Functions are central in Go. We'll learn about functions with a few different examples. |
|
package main
|
|
import "fmt"
|
|
Here's a function that takes two |
func plus(a int, b int) int {
|
Go requires explicit returns, i.e. it won't automatically return the value of the last expression. |
return a + b
}
|
When you have multiple consecutive parameters of the same type, you may omit the type name for the like-typed parameters up to the final parameter that declares the type. |
func plusPlus(a, b, c int) int {
return a + b + c
}
|
func main() {
|
|
Call a function just as you'd expect, with
|
res := plus(1, 2)
fmt.Println("1+2 =", res)
|
res = plusPlus(1, 2, 3)
fmt.Println("1+2+3 =", res)
}
|
$ go run functions.go
1+2 = 3
1+2+3 = 6
|
|
There are several other features to Go functions. One is multiple return values, which we'll look at next. |
Go has built-in support for multiple return values. This feature is used often in idiomatic Go, for example to return both result and error values from a function. |
|
package main
|
|
import "fmt"
|
|
The |
func vals() (int, int) {
return 3, 7
}
|
func main() {
|
|
Here we use the 2 different return values from the call with multiple assignment. |
a, b := vals()
fmt.Println(a)
fmt.Println(b)
|
If you only want a subset of the returned values,
use the blank identifier |
_, c := vals()
fmt.Println(c)
}
|
$ go run multiple-return-values.go
3
7
7
|
|
Accepting a variable number of arguments is another nice feature of Go functions; we'll look at this next. |
Variadic functions
can be called with any number of trailing arguments.
For example, |
|
package main
|
|
import "fmt"
|
|
Here's a function that will take an arbitrary number
of |
func sum(nums ...int) {
fmt.Print(nums, " ")
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}
|
func main() {
|
|
Variadic functions can be called in the usual way with individual arguments. |
sum(1, 2)
sum(1, 2, 3)
|
If you already have multiple args in a slice,
apply them to a variadic function using
|
nums := []int{1, 2, 3, 4}
sum(nums...)
}
|
$ go run variadic-functions.go
[1 2] 3
[1 2 3] 6
[1 2 3 4] 10
|
|
Another key aspect of functions in Go is their ability to form closures, which we'll look at next. |
Go supports anonymous functions, which can form closures. Anonymous functions are useful when you want to define a function inline without having to name it. |
|
package main
|
|
import "fmt"
|
|
This function |
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
|
func main() {
|
|
We call |
nextInt := intSeq()
|
See the effect of the closure by calling |
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
|
To confirm that the state is unique to that particular function, create and test a new one. |
newInts := intSeq()
fmt.Println(newInts())
}
|
$ go run closures.go
1
2
3
1
|
|
The last feature of functions we'll look at for now is recursion. |
Go supports recursive functions. Here's a classic factorial example. |
|
package main
|
|
import "fmt"
|
|
This |
func fact(n int) int {
if n == 0 {
return 1
}
return n * fact(n-1)
}
|
func main() {
fmt.Println(fact(7))
}
|
$ go run recursion.go
5040
|
Go supports pointers, allowing you to pass references to values and records within your program. |
|
package main
|
|
import "fmt"
|
|
We'll show how pointers work in contrast to values with
2 functions: |
func zeroval(ival int) {
ival = 0
}
|
|
func zeroptr(iptr *int) {
*iptr = 0
}
|
func main() {
i := 1
fmt.Println("initial:", i)
|
|
zeroval(i)
fmt.Println("zeroval:", i)
|
|
The |
zeroptr(&i)
fmt.Println("zeroptr:", i)
|
Pointers can be printed too. |
fmt.Println("pointer:", &i)
}
|
|
$ go run pointers.go
initial: 1
zeroval: 1
zeroptr: 0
pointer: 0x42131100
|
Go's structs are typed collections of fields. They're useful for grouping data together to form records. |
|
package main
|
|
import "fmt"
|
|
This |
type person struct {
name string
age int
}
|
NewPerson constructs a new person struct with the given name |
func NewPerson(name string) *person {
|
You can safely return a pointer to local variable as a local variable will survive the scope of the function. |
p := person{name: name}
p.age = 42
return &p
}
|
func main() {
|
|
This syntax creates a new struct. |
fmt.Println(person{"Bob", 20})
|
You can name the fields when initializing a struct. |
fmt.Println(person{name: "Alice", age: 30})
|
Omitted fields will be zero-valued. |
fmt.Println(person{name: "Fred"})
|
An |
fmt.Println(&person{name: "Ann", age: 40})
|
It's idiomatic to encapsulate new struct creation in constructor functions |
fmt.Println(NewPerson("Jon"))
|
Access struct fields with a dot. |
s := person{name: "Sean", age: 50}
fmt.Println(s.name)
|
You can also use dots with struct pointers - the pointers are automatically dereferenced. |
sp := &s
fmt.Println(sp.age)
|
Structs are mutable. |
sp.age = 51
fmt.Println(sp.age)
}
|
$ go run structs.go
{Bob 20}
{Alice 30}
{Fred 0}
&{Ann 40}
&{Jon 42}
Sean
50
51
|
Go supports methods defined on struct types. |
|
package main
|
|
import "fmt"
|
|
type rect struct {
width, height int
}
|
|
This |
func (r *rect) area() int {
return r.width * r.height
}
|
Methods can be defined for either pointer or value receiver types. Here's an example of a value receiver. |
func (r rect) perim() int {
return 2*r.width + 2*r.height
}
|
func main() {
r := rect{width: 10, height: 5}
|
|
Here we call the 2 methods defined for our struct. |
fmt.Println("area: ", r.area())
fmt.Println("perim:", r.perim())
|
Go automatically handles conversion between values and pointers for method calls. You may want to use a pointer receiver type to avoid copying on method calls or to allow the method to mutate the receiving struct. |
rp := &r
fmt.Println("area: ", rp.area())
fmt.Println("perim:", rp.perim())
}
|
$ go run methods.go
area: 50
perim: 30
area: 50
perim: 30
|
|
Next we'll look at Go's mechanism for grouping and naming related sets of methods: interfaces. |
Interfaces are named collections of method signatures. |
|
package main
|
|
import (
"fmt"
"math"
)
|
|
Here's a basic interface for geometric shapes. |
type geometry interface {
area() float64
perim() float64
}
|
For our example we'll implement this interface on
|
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
|
To implement an interface in Go, we just need to
implement all the methods in the interface. Here we
implement |
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
|
The implementation for |
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
|
If a variable has an interface type, then we can call
methods that are in the named interface. Here's a
generic |
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}
|
|
The |
measure(r)
measure(c)
}
|
$ go run interfaces.go
{3 4}
12
14
{5}
78.53981633974483
31.41592653589793
|
|
To learn more about Go's interfaces, check out this great blog post. |
In Go it's idiomatic to communicate errors via an explicit, separate return value. This contrasts with the exceptions used in languages like Java and Ruby and the overloaded single result / error value sometimes used in C. Go's approach makes it easy to see which functions return errors and to handle them using the same language constructs employed for any other, non-error tasks. |
|
package main
|
|
import (
"errors"
"fmt"
)
|
|
By convention, errors are the last return value and
have type |
func f1(arg int) (int, error) {
if arg == 42 {
|
|
return -1, errors.New("can't work with 42")
|
}
|
|
A |
return arg + 3, nil
}
|
It's possible to use custom types as |
type argError struct {
arg int
prob string
}
|
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
|
|
func f2(arg int) (int, error) {
if arg == 42 {
|
|
In this case we use |
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
|
func main() {
|
|
The two loops below test out each of our
error-returning functions. Note that the use of an
inline error check on the |
for _, i := range []int{7, 42} {
if r, e := f1(i); e != nil {
fmt.Println("f1 failed:", e)
} else {
fmt.Println("f1 worked:", r)
}
}
for _, i := range []int{7, 42} {
if r, e := f2(i); e != nil {
fmt.Println("f2 failed:", e)
} else {
fmt.Println("f2 worked:", r)
}
}
|
If you want to programmatically use the data in a custom error, you'll need to get the error as an instance of the custom error type via type assertion. |
_, e := f2(42)
if ae, ok := e.(*argError); ok {
fmt.Println(ae.arg)
fmt.Println(ae.prob)
}
}
|
$ go run errors.go
f1 worked: 10
f1 failed: can't work with 42
f2 worked: 10
f2 failed: 42 - can't work with it
42
can't work with it
|
|
See this great post on the Go blog for more on error handling. |
A goroutine is a lightweight thread of execution. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func f(from string) {
for i := 0; i < 3; i++ {
fmt.Println(from, ":", i)
}
}
|
|
func main() {
|
|
Suppose we have a function call |
f("direct")
|
To invoke this function in a goroutine, use
|
go f("goroutine")
|
You can also start a goroutine for an anonymous function call. |
go func(msg string) {
fmt.Println(msg)
}("going")
|
Our two function calls are running asynchronously in separate goroutines now. Wait for them to finish (for a more robust approach, use a WaitGroup). |
time.Sleep(time.Second)
fmt.Println("done")
}
|
When we run this program, we see the output of the blocking call first, then the interleaved output of the two goroutines. This interleaving reflects the goroutines being run concurrently by the Go runtime. |
$ go run goroutines.go
direct : 0
direct : 1
direct : 2
goroutine : 0
going
goroutine : 1
goroutine : 2
done
|
Next we'll look at a complement to goroutines in concurrent Go programs: channels. |
Channels are the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine. |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
Create a new channel with |
messages := make(chan string)
|
Send a value into a channel using the |
go func() { messages <- "ping" }()
|
The |
msg := <-messages
fmt.Println(msg)
}
|
When we run the program the |
$ go run channels.go
ping
|
By default sends and receives block until both the
sender and receiver are ready. This property allowed
us to wait at the end of our program for the |
By default channels are unbuffered, meaning that they
will only accept sends ( |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
Here we |
messages := make(chan string, 2)
|
Because this channel is buffered, we can send these values into the channel without a corresponding concurrent receive. |
messages <- "buffered"
messages <- "channel"
|
Later we can receive these two values as usual. |
fmt.Println(<-messages)
fmt.Println(<-messages)
}
|
$ go run channel-buffering.go
buffered
channel
|
We can use channels to synchronize execution across goroutines. Here's an example of using a blocking receive to wait for a goroutine to finish. When waiting for multiple goroutines to finish, you may prefer to use a WaitGroup. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
This is the function we'll run in a goroutine. The
|
func worker(done chan bool) {
fmt.Print("working...")
time.Sleep(time.Second)
fmt.Println("done")
|
Send a value to notify that we're done. |
done <- true
}
|
func main() {
|
|
Start a worker goroutine, giving it the channel to notify on. |
done := make(chan bool, 1)
go worker(done)
|
Block until we receive a notification from the worker on the channel. |
<-done
}
|
$ go run channel-synchronization.go
working...done
|
|
If you removed the |
When using channels as function parameters, you can specify if a channel is meant to only send or receive values. This specificity increases the type-safety of the program. |
|
package main
|
|
import "fmt"
|
|
This |
func ping(pings chan<- string, msg string) {
pings <- msg
}
|
The |
func pong(pings <-chan string, pongs chan<- string) {
msg := <-pings
pongs <- msg
}
|
func main() {
pings := make(chan string, 1)
pongs := make(chan string, 1)
ping(pings, "passed message")
pong(pings, pongs)
fmt.Println(<-pongs)
}
|
$ go run channel-directions.go
passed message
|
Go's select lets you wait on multiple channel operations. Combining goroutines and channels with select is a powerful feature of Go. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func main() {
|
|
For our example we'll select across two channels. |
c1 := make(chan string)
c2 := make(chan string)
|
Each channel will receive a value after some amount of time, to simulate e.g. blocking RPC operations executing in concurrent goroutines. |
go func() {
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
c2 <- "two"
}()
|
We'll use |
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2)
}
}
}
|
We receive the values |
$ time go run select.go
received one
received two
|
Note that the total execution time is only ~2 seconds
since both the 1 and 2 second |
real 0m2.245s
|
Timeouts are important for programs that connect to
external resources or that otherwise need to bound
execution time. Implementing timeouts in Go is easy and
elegant thanks to channels and |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func main() {
|
|
For our example, suppose we're executing an external
call that returns its result on a channel |
c1 := make(chan string, 1)
go func() {
time.Sleep(2 * time.Second)
c1 <- "result 1"
}()
|
Here's the |
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(1 * time.Second):
fmt.Println("timeout 1")
}
|
If we allow a longer timeout of 3s, then the receive
from |
c2 := make(chan string, 1)
go func() {
time.Sleep(2 * time.Second)
c2 <- "result 2"
}()
select {
case res := <-c2:
fmt.Println(res)
case <-time.After(3 * time.Second):
fmt.Println("timeout 2")
}
}
|
Running this program shows the first operation timing out and the second succeeding. |
$ go run timeouts.go
timeout 1
result 2
|
Basic sends and receives on channels are blocking.
However, we can use |
|
package main
|
|
import "fmt"
|
|
func main() {
messages := make(chan string)
signals := make(chan bool)
|
|
Here's a non-blocking receive. If a value is
available on |
select {
case msg := <-messages:
fmt.Println("received message", msg)
default:
fmt.Println("no message received")
}
|
A non-blocking send works similarly. Here |
msg := "hi"
select {
case messages <- msg:
fmt.Println("sent message", msg)
default:
fmt.Println("no message sent")
}
|
We can use multiple |
select {
case msg := <-messages:
fmt.Println("received message", msg)
case sig := <-signals:
fmt.Println("received signal", sig)
default:
fmt.Println("no activity")
}
}
|
$ go run non-blocking-channel-operations.go
no message received
no message sent
no activity
|
Closing a channel indicates that no more values will be sent on it. This can be useful to communicate completion to the channel's receivers. |
|
package main
|
|
import "fmt"
|
|
In this example we'll use a |
func main() {
jobs := make(chan int, 5)
done := make(chan bool)
|
Here's the worker goroutine. It repeatedly receives
from |
go func() {
for {
j, more := <-jobs
if more {
fmt.Println("received job", j)
} else {
fmt.Println("received all jobs")
done <- true
return
}
}
}()
|
This sends 3 jobs to the worker over the |
for j := 1; j <= 3; j++ {
jobs <- j
fmt.Println("sent job", j)
}
close(jobs)
fmt.Println("sent all jobs")
|
We await the worker using the synchronization approach we saw earlier. |
<-done
}
|
$ go run closing-channels.go
sent job 1
received job 1
sent job 2
received job 2
sent job 3
received job 3
sent all jobs
received all jobs
|
|
The idea of closed channels leads naturally to our next
example: |
In a previous example we saw how |
|
package main
|
|
import "fmt"
|
|
func main() {
|
|
We'll iterate over 2 values in the |
queue := make(chan string, 2)
queue <- "one"
queue <- "two"
close(queue)
|
This |
for elem := range queue {
fmt.Println(elem)
}
}
|
$ go run range-over-channels.go
one
two
|
|
This example also showed that it's possible to close a non-empty channel but still have the remaining values be received. |
We often want to execute Go code at some point in the future, or repeatedly at some interval. Go's built-in timer and ticker features make both of these tasks easy. We'll look first at timers and then at tickers. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func main() {
|
|
Timers represent a single event in the future. You tell the timer how long you want to wait, and it provides a channel that will be notified at that time. This timer will wait 2 seconds. |
timer1 := time.NewTimer(2 * time.Second)
|
The |
<-timer1.C
fmt.Println("Timer 1 fired")
|
If you just wanted to wait, you could have used
|
timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 fired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
|
Give the |
time.Sleep(2 * time.Second)
}
|
The first timer will fire ~2s after we start the program, but the second should be stopped before it has a chance to fire. |
$ go run timers.go
Timer 1 fired
Timer 2 stopped
|
Timers are for when you want to do something once in the future - tickers are for when you want to do something repeatedly at regular intervals. Here's an example of a ticker that ticks periodically until we stop it. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func main() {
|
|
Tickers use a similar mechanism to timers: a
channel that is sent values. Here we'll use the
|
ticker := time.NewTicker(500 * time.Millisecond)
done := make(chan bool)
|
go func() {
for {
select {
case <-done:
return
case t := <-ticker.C:
fmt.Println("Tick at", t)
}
}
}()
|
|
Tickers can be stopped like timers. Once a ticker is stopped it won't receive any more values on its channel. We'll stop ours after 1600ms. |
time.Sleep(1600 * time.Millisecond)
ticker.Stop()
done <- true
fmt.Println("Ticker stopped")
}
|
When we run this program the ticker should tick 3 times before we stop it. |
$ go run tickers.go
Tick at 2012-09-23 11:29:56.487625 -0700 PDT
Tick at 2012-09-23 11:29:56.988063 -0700 PDT
Tick at 2012-09-23 11:29:57.488076 -0700 PDT
Ticker stopped
|
In this example we'll look at how to implement a worker pool using goroutines and channels. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
Here's the worker, of which we'll run several
concurrent instances. These workers will receive
work on the |
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Println("worker", id, "started job", j)
time.Sleep(time.Second)
fmt.Println("worker", id, "finished job", j)
results <- j * 2
}
}
|
func main() {
|
|
In order to use our pool of workers we need to send them work and collect their results. We make 2 channels for this. |
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
|
This starts up 3 workers, initially blocked because there are no jobs yet. |
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
|
Here we send 5 |
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
|
Finally we collect all the results of the work. This also ensures that the worker goroutines have finished. An alternative way to wait for multiple goroutines is to use a WaitGroup. |
for a := 1; a <= numJobs; a++ {
<-results
}
}
|
Our running program shows the 5 jobs being executed by various workers. The program only takes about 2 seconds despite doing about 5 seconds of total work because there are 3 workers operating concurrently. |
$ time go run worker-pools.go
worker 1 started job 1
worker 2 started job 2
worker 3 started job 3
worker 1 finished job 1
worker 1 started job 4
worker 2 finished job 2
worker 2 started job 5
worker 3 finished job 3
worker 1 finished job 4
worker 2 finished job 5
|
real 0m2.358s
|
To wait for multiple goroutines to finish, we can use a wait group. |
|
package main
|
|
import (
"fmt"
"sync"
"time"
)
|
|
This is the function we'll run in every goroutine. Note that a WaitGroup must be passed to functions by pointer. |
func worker(id int, wg *sync.WaitGroup) {
|
On return, notify the WaitGroup that we're done. |
defer wg.Done()
|
fmt.Printf("Worker %d starting\n", id)
|
|
Sleep to simulate an expensive task. |
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
|
func main() {
|
|
This WaitGroup is used to wait for all the goroutines launched here to finish. |
var wg sync.WaitGroup
|
Launch several goroutines and increment the WaitGroup counter for each. |
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
|
Block until the WaitGroup counter goes back to 0; all the workers notified they're done. |
wg.Wait()
}
|
$ go run waitgroups.go
Worker 5 starting
Worker 3 starting
Worker 4 starting
Worker 1 starting
Worker 2 starting
Worker 4 done
Worker 1 done
Worker 2 done
Worker 5 done
Worker 3 done
|
|
The order of workers starting up and finishing is likely to be different for each invocation. |
Rate limiting is an important mechanism for controlling resource utilization and maintaining quality of service. Go elegantly supports rate limiting with goroutines, channels, and tickers. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func main() {
|
|
First we'll look at basic rate limiting. Suppose we want to limit our handling of incoming requests. We'll serve these requests off a channel of the same name. |
requests := make(chan int, 5)
for i := 1; i <= 5; i++ {
requests <- i
}
close(requests)
|
This |
limiter := time.Tick(200 * time.Millisecond)
|
By blocking on a receive from the |
for req := range requests {
<-limiter
fmt.Println("request", req, time.Now())
}
|
We may want to allow short bursts of requests in
our rate limiting scheme while preserving the
overall rate limit. We can accomplish this by
buffering our limiter channel. This |
burstyLimiter := make(chan time.Time, 3)
|
Fill up the channel to represent allowed bursting. |
for i := 0; i < 3; i++ {
burstyLimiter <- time.Now()
}
|
Every 200 milliseconds we'll try to add a new
value to |
go func() {
for t := range time.Tick(200 * time.Millisecond) {
burstyLimiter <- t
}
}()
|
Now simulate 5 more incoming requests. The first
3 of these will benefit from the burst capability
of |
burstyRequests := make(chan int, 5)
for i := 1; i <= 5; i++ {
burstyRequests <- i
}
close(burstyRequests)
for req := range burstyRequests {
<-burstyLimiter
fmt.Println("request", req, time.Now())
}
}
|
Running our program we see the first batch of requests handled once every ~200 milliseconds as desired. |
$ go run rate-limiting.go
request 1 2012-10-19 00:38:18.687438 +0000 UTC
request 2 2012-10-19 00:38:18.887471 +0000 UTC
request 3 2012-10-19 00:38:19.087238 +0000 UTC
request 4 2012-10-19 00:38:19.287338 +0000 UTC
request 5 2012-10-19 00:38:19.487331 +0000 UTC
|
For the second batch of requests we serve the first 3 immediately because of the burstable rate limiting, then serve the remaining 2 with ~200ms delays each. |
request 1 2012-10-19 00:38:20.487578 +0000 UTC
request 2 2012-10-19 00:38:20.487645 +0000 UTC
request 3 2012-10-19 00:38:20.487676 +0000 UTC
request 4 2012-10-19 00:38:20.687483 +0000 UTC
request 5 2012-10-19 00:38:20.887542 +0000 UTC
|
The primary mechanism for managing state in Go is
communication over channels. We saw this for example
with worker pools. There are a few other
options for managing state though. Here we'll
look at using the |
|
package main
|
|
import (
"fmt"
"sync"
"sync/atomic"
)
|
|
func main() {
|
|
We'll use an unsigned integer to represent our (always-positive) counter. |
var ops uint64
|
A WaitGroup will help us wait for all goroutines to finish their work. |
var wg sync.WaitGroup
|
We'll start 50 goroutines that each increment the counter exactly 1000 times. |
for i := 0; i < 50; i++ {
wg.Add(1)
|
To atomically increment the counter we
use |
go func() {
for c := 0; c < 1000; c++ {
|
atomic.AddUint64(&ops, 1)
}
wg.Done()
}()
}
|
|
Wait until all the goroutines are done. |
wg.Wait()
|
It's safe to access |
fmt.Println("ops:", ops)
}
|
We expect to get exactly 50,000 operations. Had we
used the non-atomic |
$ go run atomic-counters.go
ops: 50000
|
Next we'll look at mutexes, another tool for managing state. |
In the previous example we saw how to manage simple counter state using atomic operations. For more complex state we can use a mutex to safely access data across multiple goroutines. |
|
package main
|
|
import (
"fmt"
"math/rand"
"sync"
"sync/atomic"
"time"
)
|
|
func main() {
|
|
For our example the |
var state = make(map[int]int)
|
This |
var mutex = &sync.Mutex{}
|
We'll keep track of how many read and write operations we do. |
var readOps uint64
var writeOps uint64
|
Here we start 100 goroutines to execute repeated reads against the state, once per millisecond in each goroutine. |
for r := 0; r < 100; r++ {
go func() {
total := 0
for {
|
For each read we pick a key to access,
|
key := rand.Intn(5)
mutex.Lock()
total += state[key]
mutex.Unlock()
atomic.AddUint64(&readOps, 1)
|
Wait a bit between reads. |
time.Sleep(time.Millisecond)
}
}()
}
|
We'll also start 10 goroutines to simulate writes, using the same pattern we did for reads. |
for w := 0; w < 10; w++ {
go func() {
for {
key := rand.Intn(5)
val := rand.Intn(100)
mutex.Lock()
state[key] = val
mutex.Unlock()
atomic.AddUint64(&writeOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
|
Let the 10 goroutines work on the |
time.Sleep(time.Second)
|
Take and report final operation counts. |
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
|
With a final lock of |
mutex.Lock()
fmt.Println("state:", state)
mutex.Unlock()
}
|
Running the program shows that we executed about
90,000 total operations against our |
$ go run mutexes.go
readOps: 83285
writeOps: 8320
state: map[1:97 4:53 0:33 2:15 3:2]
|
Next we'll look at implementing this same state management task using only goroutines and channels. |
In the previous example we used explicit locking with mutexes to synchronize access to shared state across multiple goroutines. Another option is to use the built-in synchronization features of goroutines and channels to achieve the same result. This channel-based approach aligns with Go's ideas of sharing memory by communicating and having each piece of data owned by exactly 1 goroutine. |
|
package main
|
|
import (
"fmt"
"math/rand"
"sync/atomic"
"time"
)
|
|
In this example our state will be owned by a single
goroutine. This will guarantee that the data is never
corrupted with concurrent access. In order to read or
write that state, other goroutines will send messages
to the owning goroutine and receive corresponding
replies. These |
type readOp struct {
key int
resp chan int
}
type writeOp struct {
key int
val int
resp chan bool
}
|
func main() {
|
|
As before we'll count how many operations we perform. |
var readOps uint64
var writeOps uint64
|
The |
reads := make(chan readOp)
writes := make(chan writeOp)
|
Here is the goroutine that owns the |
go func() {
var state = make(map[int]int)
for {
select {
case read := <-reads:
read.resp <- state[read.key]
case write := <-writes:
state[write.key] = write.val
write.resp <- true
}
}
}()
|
This starts 100 goroutines to issue reads to the
state-owning goroutine via the |
for r := 0; r < 100; r++ {
go func() {
for {
read := readOp{
key: rand.Intn(5),
resp: make(chan int)}
reads <- read
<-read.resp
atomic.AddUint64(&readOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
|
We start 10 writes as well, using a similar approach. |
for w := 0; w < 10; w++ {
go func() {
for {
write := writeOp{
key: rand.Intn(5),
val: rand.Intn(100),
resp: make(chan bool)}
writes <- write
<-write.resp
atomic.AddUint64(&writeOps, 1)
time.Sleep(time.Millisecond)
}
}()
}
|
Let the goroutines work for a second. |
time.Sleep(time.Second)
|
Finally, capture and report the op counts. |
readOpsFinal := atomic.LoadUint64(&readOps)
fmt.Println("readOps:", readOpsFinal)
writeOpsFinal := atomic.LoadUint64(&writeOps)
fmt.Println("writeOps:", writeOpsFinal)
}
|
Running our program shows that the goroutine-based state management example completes about 80,000 total operations. |
$ go run stateful-goroutines.go
readOps: 71708
writeOps: 7177
|
For this particular case the goroutine-based approach was a bit more involved than the mutex-based one. It might be useful in certain cases though, for example where you have other channels involved or when managing multiple such mutexes would be error-prone. You should use whichever approach feels most natural, especially with respect to understanding the correctness of your program. |
Go's |
|
package main
|
|
import (
"fmt"
"sort"
)
|
|
func main() {
|
|
Sort methods are specific to the builtin type; here's an example for strings. Note that sorting is in-place, so it changes the given slice and doesn't return a new one. |
strs := []string{"c", "a", "b"}
sort.Strings(strs)
fmt.Println("Strings:", strs)
|
An example of sorting |
ints := []int{7, 2, 4}
sort.Ints(ints)
fmt.Println("Ints: ", ints)
|
We can also use |
s := sort.IntsAreSorted(ints)
fmt.Println("Sorted: ", s)
}
|
Running our program prints the sorted string and int
slices and |
$ go run sorting.go
Strings: [a b c]
Ints: [2 4 7]
Sorted: true
|
Sometimes we'll want to sort a collection by something other than its natural order. For example, suppose we wanted to sort strings by their length instead of alphabetically. Here's an example of custom sorts in Go. |
|
package main
|
|
import (
"fmt"
"sort"
)
|
|
In order to sort by a custom function in Go, we need a
corresponding type. Here we've created a |
type byLength []string
|
We implement |
func (s byLength) Len() int {
return len(s)
}
func (s byLength) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s byLength) Less(i, j int) bool {
return len(s[i]) < len(s[j])
}
|
With all of this in place, we can now implement our
custom sort by converting the original |
func main() {
fruits := []string{"peach", "banana", "kiwi"}
sort.Sort(byLength(fruits))
fmt.Println(fruits)
}
|
Running our program shows a list sorted by string length, as desired. |
$ go run sorting-by-functions.go
[kiwi peach banana]
|
By following this same pattern of creating a custom
type, implementing the three |
A |
|
package main
|
|
import "os"
|
|
func main() {
|
|
We'll use panic throughout this site to check for unexpected errors. This is the only program on the site designed to panic. |
panic("a problem")
|
A common use of panic is to abort if a function
returns an error value that we don't know how to
(or want to) handle. Here's an example of
|
_, err := os.Create("/tmp/file")
if err != nil {
panic(err)
}
}
|
Running this program will cause it to panic, print an error message and goroutine traces, and exit with a non-zero status. |
$ go run panic.go
panic: a problem
|
goroutine 1 [running]:
main.main()
/.../panic.go:12 +0x47
...
exit status 2
|
|
Note that unlike some languages which use exceptions for handling of many errors, in Go it is idiomatic to use error-indicating return values wherever possible. |
Defer is used to ensure that a function call is
performed later in a program's execution, usually for
purposes of cleanup. |
|
package main
|
|
import (
"fmt"
"os"
)
|
|
Suppose we wanted to create a file, write to it,
and then close when we're done. Here's how we could
do that with |
func main() {
|
Immediately after getting a file object with
|
f := createFile("/tmp/defer.txt")
defer closeFile(f)
writeFile(f)
}
|
func createFile(p string) *os.File {
fmt.Println("creating")
f, err := os.Create(p)
if err != nil {
panic(err)
}
return f
}
|
|
func writeFile(f *os.File) {
fmt.Println("writing")
fmt.Fprintln(f, "data")
|
|
}
|
|
It's important to check for errors when closing a file, even in a deferred function. |
func closeFile(f *os.File) {
fmt.Println("closing")
err := f.Close()
|
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
|
Running the program confirms that the file is closed after being written. |
$ go run defer.go
creating
writing
closing
|
We often need our programs to perform operations on collections of data, like selecting all items that satisfy a given predicate or mapping all items to a new collection with a custom function. |
|
In some languages it's idiomatic to use generic data structures and algorithms. Go does not support generics; in Go it's common to provide collection functions if and when they are specifically needed for your program and data types. |
|
Here are some example collection functions for slices
of |
|
package main
|
|
import (
"fmt"
"strings"
)
|
|
Index returns the first index of the target string |
func Index(vs []string, t string) int {
for i, v := range vs {
if v == t {
return i
}
}
return -1
}
|
Include returns |
func Include(vs []string, t string) bool {
return Index(vs, t) >= 0
}
|
Any returns |
func Any(vs []string, f func(string) bool) bool {
for _, v := range vs {
if f(v) {
return true
}
}
return false
}
|
All returns |
func All(vs []string, f func(string) bool) bool {
for _, v := range vs {
if !f(v) {
return false
}
}
return true
}
|
Filter returns a new slice containing all strings in the
slice that satisfy the predicate |
func Filter(vs []string, f func(string) bool) []string {
vsf := make([]string, 0)
for _, v := range vs {
if f(v) {
vsf = append(vsf, v)
}
}
return vsf
}
|
Map returns a new slice containing the results of applying
the function |
func Map(vs []string, f func(string) string) []string {
vsm := make([]string, len(vs))
for i, v := range vs {
vsm[i] = f(v)
}
return vsm
}
|
func main() {
|
|
Here we try out our various collection functions. |
var strs = []string{"peach", "apple", "pear", "plum"}
|
fmt.Println(Index(strs, "pear"))
|
|
fmt.Println(Include(strs, "grape"))
|
|
fmt.Println(Any(strs, func(v string) bool {
return strings.HasPrefix(v, "p")
}))
|
|
fmt.Println(All(strs, func(v string) bool {
return strings.HasPrefix(v, "p")
}))
|
|
fmt.Println(Filter(strs, func(v string) bool {
return strings.Contains(v, "e")
}))
|
|
The above examples all used anonymous functions, but you can also use named functions of the correct type. |
fmt.Println(Map(strs, strings.ToUpper))
|
}
|
$ go run collection-functions.go
2
false
true
false
[peach apple pear]
[PEACH APPLE PEAR PLUM]
|
The standard library's |
|
package main
|
|
import (
"fmt"
s "strings"
)
|
|
We alias |
var p = fmt.Println
|
func main() {
|
|
Here's a sample of the functions available in
|
p("Contains: ", s.Contains("test", "es"))
p("Count: ", s.Count("test", "t"))
p("HasPrefix: ", s.HasPrefix("test", "te"))
p("HasSuffix: ", s.HasSuffix("test", "st"))
p("Index: ", s.Index("test", "e"))
p("Join: ", s.Join([]string{"a", "b"}, "-"))
p("Repeat: ", s.Repeat("a", 5))
p("Replace: ", s.Replace("foo", "o", "0", -1))
p("Replace: ", s.Replace("foo", "o", "0", 1))
p("Split: ", s.Split("a-b-c-d-e", "-"))
p("ToLower: ", s.ToLower("TEST"))
p("ToUpper: ", s.ToUpper("test"))
p()
|
Not part of |
p("Len: ", len("hello"))
p("Char:", "hello"[1])
}
|
Note that |
$ go run string-functions.go
Contains: true
Count: 2
HasPrefix: true
HasSuffix: true
Index: 1
Join: a-b
Repeat: aaaaa
Replace: f00
Replace: f0o
Split: [a b c d e]
ToLower: test
ToUpper: TEST
|
|
Len: 5
Char: 101
|
Go offers excellent support for string formatting in
the |
|
package main
|
|
import (
"fmt"
"os"
)
|
|
type point struct {
x, y int
}
|
|
func main() {
|
|
Go offers several printing "verbs" designed to
format general Go values. For example, this prints
an instance of our |
p := point{1, 2}
fmt.Printf("%v\n", p)
|
If the value is a struct, the |
fmt.Printf("%+v\n", p)
|
The |
fmt.Printf("%#v\n", p)
|
To print the type of a value, use |
fmt.Printf("%T\n", p)
|
Formatting booleans is straight-forward. |
fmt.Printf("%t\n", true)
|
There are many options for formatting integers.
Use |
fmt.Printf("%d\n", 123)
|
This prints a binary representation. |
fmt.Printf("%b\n", 14)
|
This prints the character corresponding to the given integer. |
fmt.Printf("%c\n", 33)
|
|
fmt.Printf("%x\n", 456)
|
There are also several formatting options for
floats. For basic decimal formatting use |
fmt.Printf("%f\n", 78.9)
|
|
fmt.Printf("%e\n", 123400000.0)
fmt.Printf("%E\n", 123400000.0)
|
For basic string printing use |
fmt.Printf("%s\n", "\"string\"")
|
To double-quote strings as in Go source, use |
fmt.Printf("%q\n", "\"string\"")
|
As with integers seen earlier, |
fmt.Printf("%x\n", "hex this")
|
To print a representation of a pointer, use |
fmt.Printf("%p\n", &p)
|
When formatting numbers you will often want to
control the width and precision of the resulting
figure. To specify the width of an integer, use a
number after the |
fmt.Printf("|%6d|%6d|\n", 12, 345)
|
You can also specify the width of printed floats, though usually you'll also want to restrict the decimal precision at the same time with the width.precision syntax. |
fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)
|
To left-justify, use the |
fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)
|
You may also want to control width when formatting strings, especially to ensure that they align in table-like output. For basic right-justified width. |
fmt.Printf("|%6s|%6s|\n", "foo", "b")
|
To left-justify use the |
fmt.Printf("|%-6s|%-6s|\n", "foo", "b")
|
So far we've seen |
s := fmt.Sprintf("a %s", "string")
fmt.Println(s)
|
You can format+print to |
fmt.Fprintf(os.Stderr, "an %s\n", "error")
}
|
$ go run string-formatting.go
{1 2}
{x:1 y:2}
main.point{x:1, y:2}
main.point
true
123
1110
!
1c8
78.900000
1.234000e+08
1.234000E+08
"string"
"\"string\""
6865782074686973
0x42135100
| 12| 345|
| 1.20| 3.45|
|1.20 |3.45 |
| foo| b|
|foo |b |
a string
an error
|
Go offers built-in support for regular expressions. Here are some examples of common regexp-related tasks in Go. |
|
package main
|
|
import (
"bytes"
"fmt"
"regexp"
)
|
|
func main() {
|
|
This tests whether a pattern matches a string. |
match, _ := regexp.MatchString("p([a-z]+)ch", "peach")
fmt.Println(match)
|
Above we used a string pattern directly, but for
other regexp tasks you'll need to |
r, _ := regexp.Compile("p([a-z]+)ch")
|
Many methods are available on these structs. Here's a match test like we saw earlier. |
fmt.Println(r.MatchString("peach"))
|
This finds the match for the regexp. |
fmt.Println(r.FindString("peach punch"))
|
This also finds the first match but returns the start and end indexes for the match instead of the matching text. |
fmt.Println(r.FindStringIndex("peach punch"))
|
The |
fmt.Println(r.FindStringSubmatch("peach punch"))
|
Similarly this will return information about the indexes of matches and submatches. |
fmt.Println(r.FindStringSubmatchIndex("peach punch"))
|
The |
fmt.Println(r.FindAllString("peach punch pinch", -1))
|
These |
fmt.Println(r.FindAllStringSubmatchIndex(
"peach punch pinch", -1))
|
Providing a non-negative integer as the second argument to these functions will limit the number of matches. |
fmt.Println(r.FindAllString("peach punch pinch", 2))
|
Our examples above had string arguments and used
names like |
fmt.Println(r.Match([]byte("peach")))
|
When creating constants with regular expressions
you can use the |
r = regexp.MustCompile("p([a-z]+)ch")
fmt.Println(r)
|
The |
fmt.Println(r.ReplaceAllString("a peach", "<fruit>"))
|
The |
in := []byte("a peach")
out := r.ReplaceAllFunc(in, bytes.ToUpper)
fmt.Println(string(out))
}
|
$ go run regular-expressions.go
true
true
peach
[0 5]
[peach ea]
[0 5 1 3]
[peach punch pinch]
[[0 5 1 3] [6 11 7 9] [12 17 13 15]]
[peach punch]
true
p([a-z]+)ch
a <fruit>
a PEACH
|
|
For a complete reference on Go regular expressions check
the |
Go offers built-in support for JSON encoding and decoding, including to and from built-in and custom data types. |
|
package main
|
|
import (
"encoding/json"
"fmt"
"os"
)
|
|
We'll use these two structs to demonstrate encoding and decoding of custom types below. |
type response1 struct {
Page int
Fruits []string
}
|
Only exported fields will be encoded/decoded in JSON. Fields must start with capital letters to be exported. |
type response2 struct {
Page int `json:"page"`
Fruits []string `json:"fruits"`
}
|
func main() {
|
|
First we'll look at encoding basic data types to JSON strings. Here are some examples for atomic values. |
bolB, _ := json.Marshal(true)
fmt.Println(string(bolB))
|
intB, _ := json.Marshal(1)
fmt.Println(string(intB))
|
|
fltB, _ := json.Marshal(2.34)
fmt.Println(string(fltB))
|
|
strB, _ := json.Marshal("gopher")
fmt.Println(string(strB))
|
|
And here are some for slices and maps, which encode to JSON arrays and objects as you'd expect. |
slcD := []string{"apple", "peach", "pear"}
slcB, _ := json.Marshal(slcD)
fmt.Println(string(slcB))
|
mapD := map[string]int{"apple": 5, "lettuce": 7}
mapB, _ := json.Marshal(mapD)
fmt.Println(string(mapB))
|
|
The JSON package can automatically encode your custom data types. It will only include exported fields in the encoded output and will by default use those names as the JSON keys. |
res1D := &response1{
Page: 1,
Fruits: []string{"apple", "peach", "pear"}}
res1B, _ := json.Marshal(res1D)
fmt.Println(string(res1B))
|
You can use tags on struct field declarations
to customize the encoded JSON key names. Check the
definition of |
res2D := &response2{
Page: 1,
Fruits: []string{"apple", "peach", "pear"}}
res2B, _ := json.Marshal(res2D)
fmt.Println(string(res2B))
|
Now let's look at decoding JSON data into Go values. Here's an example for a generic data structure. |
byt := []byte(`{"num":6.13,"strs":["a","b"]}`)
|
We need to provide a variable where the JSON
package can put the decoded data. This
|
var dat map[string]interface{}
|
Here's the actual decoding, and a check for associated errors. |
if err := json.Unmarshal(byt, &dat); err != nil {
panic(err)
}
fmt.Println(dat)
|
In order to use the values in the decoded map,
we'll need to convert them to their appropriate type.
For example here we convert the value in |
num := dat["num"].(float64)
fmt.Println(num)
|
Accessing nested data requires a series of conversions. |
strs := dat["strs"].([]interface{})
str1 := strs[0].(string)
fmt.Println(str1)
|
We can also decode JSON into custom data types. This has the advantages of adding additional type-safety to our programs and eliminating the need for type assertions when accessing the decoded data. |
str := `{"page": 1, "fruits": ["apple", "peach"]}`
res := response2{}
json.Unmarshal([]byte(str), &res)
fmt.Println(res)
fmt.Println(res.Fruits[0])
|
In the examples above we always used bytes and
strings as intermediates between the data and
JSON representation on standard out. We can also
stream JSON encodings directly to |
enc := json.NewEncoder(os.Stdout)
d := map[string]int{"apple": 5, "lettuce": 7}
enc.Encode(d)
}
|
$ go run json.go
true
1
2.34
"gopher"
["apple","peach","pear"]
{"apple":5,"lettuce":7}
{"Page":1,"Fruits":["apple","peach","pear"]}
{"page":1,"fruits":["apple","peach","pear"]}
map[num:6.13 strs:[a b]]
6.13
a
{1 [apple peach]}
apple
{"apple":5,"lettuce":7}
|
|
We've covered the basic of JSON in Go here, but check out the JSON and Go blog post and JSON package docs for more. |
Go offers built-in support for XML and XML-like
formats with the |
|
package main
|
|
import (
"encoding/xml"
"fmt"
)
|
|
This type will be mapped to XML. Similarly to the
JSON examples, field tags contain directives for the
encoder and decoder. Here we use some special features
of the XML package: the |
type Plant struct {
XMLName xml.Name `xml:"plant"`
Id int `xml:"id,attr"`
Name string `xml:"name"`
Origin []string `xml:"origin"`
}
|
func (p Plant) String() string {
return fmt.Sprintf("Plant id=%v, name=%v, origin=%v",
p.Id, p.Name, p.Origin)
}
|
|
func main() {
coffee := &Plant{Id: 27, Name: "Coffee"}
coffee.Origin = []string{"Ethiopia", "Brazil"}
|
|
Emit XML representing our plant; using
|
out, _ := xml.MarshalIndent(coffee, " ", " ")
fmt.Println(string(out))
|
To add a generic XML header to the output, append it explicitly. |
fmt.Println(xml.Header + string(out))
|
Use |
var p Plant
if err := xml.Unmarshal(out, &p); err != nil {
panic(err)
}
fmt.Println(p)
|
tomato := &Plant{Id: 81, Name: "Tomato"}
tomato.Origin = []string{"Mexico", "California"}
|
|
The |
type Nesting struct {
XMLName xml.Name `xml:"nesting"`
Plants []*Plant `xml:"parent>child>plant"`
}
|
nesting := &Nesting{}
nesting.Plants = []*Plant{coffee, tomato}
|
|
out, _ = xml.MarshalIndent(nesting, " ", " ")
fmt.Println(string(out))
}
|
$ go run xml.go
<plant id="27">
<name>Coffee</name>
<origin>Ethiopia</origin>
<origin>Brazil</origin>
</plant>
<?xml version="1.0" encoding="UTF-8"?>
<plant id="27">
<name>Coffee</name>
<origin>Ethiopia</origin>
<origin>Brazil</origin>
</plant>
Plant id=27, name=Coffee, origin=[Ethiopia Brazil]
<nesting>
<parent>
<child>
<plant id="27">
<name>Coffee</name>
<origin>Ethiopia</origin>
<origin>Brazil</origin>
</plant>
<plant id="81">
<name>Tomato</name>
<origin>Mexico</origin>
<origin>California</origin>
</plant>
</child>
</parent>
</nesting>
|
Go offers extensive support for times and durations; here are some examples. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func main() {
p := fmt.Println
|
|
We'll start by getting the current time. |
now := time.Now()
p(now)
|
You can build a |
then := time.Date(
2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
p(then)
|
You can extract the various components of the time value as expected. |
p(then.Year())
p(then.Month())
p(then.Day())
p(then.Hour())
p(then.Minute())
p(then.Second())
p(then.Nanosecond())
p(then.Location())
|
The Monday-Sunday |
p(then.Weekday())
|
These methods compare two times, testing if the first occurs before, after, or at the same time as the second, respectively. |
p(then.Before(now))
p(then.After(now))
p(then.Equal(now))
|
The |
diff := now.Sub(then)
p(diff)
|
We can compute the length of the duration in various units. |
p(diff.Hours())
p(diff.Minutes())
p(diff.Seconds())
p(diff.Nanoseconds())
|
You can use |
p(then.Add(diff))
p(then.Add(-diff))
}
|
$ go run time.go
2012-10-31 15:50:13.793654 +0000 UTC
2009-11-17 20:34:58.651387237 +0000 UTC
2009
November
17
20
34
58
651387237
UTC
Tuesday
true
false
false
25891h15m15.142266763s
25891.25420618521
1.5534752523711128e+06
9.320851514226677e+07
93208515142266763
2012-10-31 15:50:13.793654 +0000 UTC
2006-12-05 01:19:43.509120474 +0000 UTC
|
|
Next we'll look at the related idea of time relative to the Unix epoch. |
A common requirement in programs is getting the number of seconds, milliseconds, or nanoseconds since the Unix epoch. Here's how to do it in Go. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func main() {
|
|
Use |
now := time.Now()
secs := now.Unix()
nanos := now.UnixNano()
fmt.Println(now)
|
Note that there is no |
millis := nanos / 1000000
fmt.Println(secs)
fmt.Println(millis)
fmt.Println(nanos)
|
You can also convert integer seconds or nanoseconds
since the epoch into the corresponding |
fmt.Println(time.Unix(secs, 0))
fmt.Println(time.Unix(0, nanos))
}
|
$ go run epoch.go
2012-10-31 16:13:58.292387 +0000 UTC
1351700038
1351700038292
1351700038292387000
2012-10-31 16:13:58 +0000 UTC
2012-10-31 16:13:58.292387 +0000 UTC
|
|
Next we'll look at another time-related task: time parsing and formatting. |
Go supports time formatting and parsing via pattern-based layouts. |
|
package main
|
|
import (
"fmt"
"time"
)
|
|
func main() {
p := fmt.Println
|
|
Here's a basic example of formatting a time according to RFC3339, using the corresponding layout constant. |
t := time.Now()
p(t.Format(time.RFC3339))
|
Time parsing uses the same layout values as |
t1, e := time.Parse(
time.RFC3339,
"2012-11-01T22:08:41+00:00")
p(t1)
|
|
p(t.Format("3:04PM"))
p(t.Format("Mon Jan _2 15:04:05 2006"))
p(t.Format("2006-01-02T15:04:05.999999-07:00"))
form := "3 04 PM"
t2, e := time.Parse(form, "8 41 PM")
p(t2)
|
For purely numeric representations you can also use standard string formatting with the extracted components of the time value. |
fmt.Printf("%d-%02d-%02dT%02d:%02d:%02d-00:00\n",
t.Year(), t.Month(), t.Day(),
t.Hour(), t.Minute(), t.Second())
|
|
ansic := "Mon Jan _2 15:04:05 2006"
_, e = time.Parse(ansic, "8:41PM")
p(e)
}
|
$ go run time-formatting-parsing.go
2014-04-15T18:00:15-07:00
2012-11-01 22:08:41 +0000 +0000
6:00PM
Tue Apr 15 18:00:15 2014
2014-04-15T18:00:15.161182-07:00
0000-01-01 20:41:00 +0000 UTC
2014-04-15T18:00:15-00:00
parsing time "8:41PM" as "Mon Jan _2 15:04:05 2006": ...
|
Go's |
|
package main
|
|
import (
"fmt"
"math/rand"
"time"
)
|
|
func main() {
|
|
For example, |
fmt.Print(rand.Intn(100), ",")
fmt.Print(rand.Intn(100))
fmt.Println()
|
|
fmt.Println(rand.Float64())
|
This can be used to generate random floats in
other ranges, for example |
fmt.Print((rand.Float64()*5)+5, ",")
fmt.Print((rand.Float64() * 5) + 5)
fmt.Println()
|
The default number generator is deterministic, so it'll
produce the same sequence of numbers each time by default.
To produce varying sequences, give it a seed that changes.
Note that this is not safe to use for random numbers you
intend to be secret, use |
s1 := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(s1)
|
Call the resulting |
fmt.Print(r1.Intn(100), ",")
fmt.Print(r1.Intn(100))
fmt.Println()
|
If you seed a source with the same number, it produces the same sequence of random numbers. |
s2 := rand.NewSource(42)
r2 := rand.New(s2)
fmt.Print(r2.Intn(100), ",")
fmt.Print(r2.Intn(100))
fmt.Println()
s3 := rand.NewSource(42)
r3 := rand.New(s3)
fmt.Print(r3.Intn(100), ",")
fmt.Print(r3.Intn(100))
}
|
$ go run random-numbers.go
81,87
0.6645600532184904
7.123187485356329,8.434115364335547
0,28
5,87
5,87
|
|
See the |
Parsing numbers from strings is a basic but common task in many programs; here's how to do it in Go. |
|
package main
|
|
The built-in package |
import (
"fmt"
"strconv"
)
|
func main() {
|
|
With |
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f)
|
For |
i, _ := strconv.ParseInt("123", 0, 64)
fmt.Println(i)
|
|
d, _ := strconv.ParseInt("0x1c8", 0, 64)
fmt.Println(d)
|
A |
u, _ := strconv.ParseUint("789", 0, 64)
fmt.Println(u)
|
|
k, _ := strconv.Atoi("135")
fmt.Println(k)
|
Parse functions return an error on bad input. |
_, e := strconv.Atoi("wat")
fmt.Println(e)
}
|
$ go run number-parsing.go
1.234
123
456
789
135
strconv.ParseInt: parsing "wat": invalid syntax
|
|
Next we'll look at another common parsing task: URLs. |
URLs provide a uniform way to locate resources. Here's how to parse URLs in Go. |
|
package main
|
|
import (
"fmt"
"net"
"net/url"
)
|
|
func main() {
|
|
We'll parse this example URL, which includes a scheme, authentication info, host, port, path, query params, and query fragment. |
s := "postgres://user:pass@host.com:5432/path?k=v#f"
|
Parse the URL and ensure there are no errors. |
u, err := url.Parse(s)
if err != nil {
panic(err)
}
|
Accessing the scheme is straightforward. |
fmt.Println(u.Scheme)
|
|
fmt.Println(u.User)
fmt.Println(u.User.Username())
p, _ := u.User.Password()
fmt.Println(p)
|
The |
fmt.Println(u.Host)
host, port, _ := net.SplitHostPort(u.Host)
fmt.Println(host)
fmt.Println(port)
|
Here we extract the |
fmt.Println(u.Path)
fmt.Println(u.Fragment)
|
To get query params in a string of |
fmt.Println(u.RawQuery)
m, _ := url.ParseQuery(u.RawQuery)
fmt.Println(m)
fmt.Println(m["k"][0])
}
|
Running our URL parsing program shows all the different pieces that we extracted. |
$ go run url-parsing.go
postgres
user:pass
user
pass
host.com:5432
host.com
5432
/path
f
k=v
map[k:[v]]
v
|
SHA1 hashes are frequently used to compute short identities for binary or text blobs. For example, the git revision control system uses SHA1s extensively to identify versioned files and directories. Here's how to compute SHA1 hashes in Go. |
|
package main
|
|
Go implements several hash functions in various
|
import (
"crypto/sha1"
"fmt"
)
|
func main() {
s := "sha1 this string"
|
|
The pattern for generating a hash is |
h := sha1.New()
|
|
h.Write([]byte(s))
|
This gets the finalized hash result as a byte
slice. The argument to |
bs := h.Sum(nil)
|
SHA1 values are often printed in hex, for example
in git commits. Use the |
fmt.Println(s)
fmt.Printf("%x\n", bs)
}
|
Running the program computes the hash and prints it in a human-readable hex format. |
$ go run sha1-hashes.go
sha1 this string
cf23df2207d99a74fbe169e3eba035e633b65d94
|
You can compute other hashes using a similar pattern to
the one shown above. For example, to compute MD5 hashes
import |
|
Note that if you need cryptographically secure hashes, you should carefully research hash strength! |
Go provides built-in support for base64 encoding/decoding. |
|
package main
|
|
This syntax imports the |
import (
b64 "encoding/base64"
"fmt"
)
|
func main() {
|
|
Here's the |
data := "abc123!?$*&()'-=@~"
|
Go supports both standard and URL-compatible
base64. Here's how to encode using the standard
encoder. The encoder requires a |
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
fmt.Println(sEnc)
|
Decoding may return an error, which you can check if you don't already know the input to be well-formed. |
sDec, _ := b64.StdEncoding.DecodeString(sEnc)
fmt.Println(string(sDec))
fmt.Println()
|
This encodes/decodes using a URL-compatible base64 format. |
uEnc := b64.URLEncoding.EncodeToString([]byte(data))
fmt.Println(uEnc)
uDec, _ := b64.URLEncoding.DecodeString(uEnc)
fmt.Println(string(uDec))
}
|
The string encodes to slightly different values with the
standard and URL base64 encoders (trailing |
$ go run base64-encoding.go
YWJjMTIzIT8kKiYoKSctPUB+
abc123!?$*&()'-=@~
|
YWJjMTIzIT8kKiYoKSctPUB-
abc123!?$*&()'-=@~
|
Reading and writing files are basic tasks needed for many Go programs. First we'll look at some examples of reading files. |
|
package main
|
|
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
)
|
|
Reading files requires checking most calls for errors. This helper will streamline our error checks below. |
func check(e error) {
if e != nil {
panic(e)
}
}
|
func main() {
|
|
Perhaps the most basic file reading task is slurping a file's entire contents into memory. |
dat, err := ioutil.ReadFile("/tmp/dat")
check(err)
fmt.Print(string(dat))
|
You'll often want more control over how and what
parts of a file are read. For these tasks, start
by |
f, err := os.Open("/tmp/dat")
check(err)
|
Read some bytes from the beginning of the file. Allow up to 5 to be read but also note how many actually were read. |
b1 := make([]byte, 5)
n1, err := f.Read(b1)
check(err)
fmt.Printf("%d bytes: %s\n", n1, string(b1[:n1]))
|
You can also |
o2, err := f.Seek(6, 0)
check(err)
b2 := make([]byte, 2)
n2, err := f.Read(b2)
check(err)
fmt.Printf("%d bytes @ %d: ", n2, o2)
fmt.Printf("%v\n", string(b2[:n2]))
|
The |
o3, err := f.Seek(6, 0)
check(err)
b3 := make([]byte, 2)
n3, err := io.ReadAtLeast(f, b3, 2)
check(err)
fmt.Printf("%d bytes @ %d: %s\n", n3, o3, string(b3))
|
There is no built-in rewind, but |
_, err = f.Seek(0, 0)
check(err)
|
The |
r4 := bufio.NewReader(f)
b4, err := r4.Peek(5)
check(err)
fmt.Printf("5 bytes: %s\n", string(b4))
|
Close the file when you're done (usually this would
be scheduled immediately after |
f.Close()
}
|
$ echo "hello" > /tmp/dat
$ echo "go" >> /tmp/dat
$ go run reading-files.go
hello
go
5 bytes: hello
2 bytes @ 6: go
2 bytes @ 6: go
5 bytes: hello
|
|
Next we'll look at writing files. |
Writing files in Go follows similar patterns to the ones we saw earlier for reading. |
|
package main
|
|
import (
"bufio"
"fmt"
"io/ioutil"
"os"
)
|
|
func check(e error) {
if e != nil {
panic(e)
}
}
|
|
func main() {
|
|
To start, here's how to dump a string (or just bytes) into a file. |
d1 := []byte("hello\ngo\n")
err := ioutil.WriteFile("/tmp/dat1", d1, 0644)
check(err)
|
For more granular writes, open a file for writing. |
f, err := os.Create("/tmp/dat2")
check(err)
|
It's idiomatic to defer a |
defer f.Close()
|
You can |
d2 := []byte{115, 111, 109, 101, 10}
n2, err := f.Write(d2)
check(err)
fmt.Printf("wrote %d bytes\n", n2)
|
A |
n3, err := f.WriteString("writes\n")
fmt.Printf("wrote %d bytes\n", n3)
|
Issue a |
f.Sync()
|
|
w := bufio.NewWriter(f)
n4, err := w.WriteString("buffered\n")
fmt.Printf("wrote %d bytes\n", n4)
|
Use |
w.Flush()
|
}
|
Try running the file-writing code. |
$ go run writing-files.go
wrote 5 bytes
wrote 7 bytes
wrote 9 bytes
|
Then check the contents of the written files. |
$ cat /tmp/dat1
hello
go
$ cat /tmp/dat2
some
writes
buffered
|
Next we'll look at applying some of the file I/O ideas
we've just seen to the |
A line filter is a common type of program that reads
input on stdin, processes it, and then prints some
derived result to stdout. |
|
Here's an example line filter in Go that writes a capitalized version of all input text. You can use this pattern to write your own Go line filters. |
package main
|
import (
"bufio"
"fmt"
"os"
"strings"
)
|
|
func main() {
|
|
Wrapping the unbuffered |
scanner := bufio.NewScanner(os.Stdin)
|
|
for scanner.Scan() {
|
ucl := strings.ToUpper(scanner.Text())
|
|
Write out the uppercased line. |
fmt.Println(ucl)
}
|
Check for errors during |
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
|
To try out our line filter, first make a file with a few lowercase lines. |
$ echo 'hello' > /tmp/lines
$ echo 'filter' >> /tmp/lines
|
Then use the line filter to get uppercase lines. |
$ cat /tmp/lines | go run line-filters.go
HELLO
FILTER
|
The |
package main
|
import (
"fmt"
"path/filepath"
"strings"
)
|
|
func main() {
|
|
|
p := filepath.Join("dir1", "dir2", "filename")
fmt.Println("p:", p)
|
You should always use |
fmt.Println(filepath.Join("dir1//", "filename"))
fmt.Println(filepath.Join("dir1/../dir1", "filename"))
|
|
fmt.Println("Dir(p):", filepath.Dir(p))
fmt.Println("Base(p):", filepath.Base(p))
|
We can check whether a path is absolute. |
fmt.Println(filepath.IsAbs("dir/file"))
fmt.Println(filepath.IsAbs("/dir/file"))
|
filename := "config.json"
|
|
Some file names have extensions following a dot. We
can split the extension out of such names with |
ext := filepath.Ext(filename)
fmt.Println(ext)
|
To find the file's name with the extension removed,
use |
fmt.Println(strings.TrimSuffix(filename, ext))
|
|
rel, err := filepath.Rel("a/b", "a/b/t/file")
if err != nil {
panic(err)
}
fmt.Println(rel)
|
rel, err = filepath.Rel("a/b", "a/c/t/file")
if err != nil {
panic(err)
}
fmt.Println(rel)
}
|
$ go run file-paths.go
p: dir1/dir2/filename
dir1/filename
dir1/filename
Dir(p): dir1/dir2
Base(p): filename
false
true
.json
config
t/file
../c/t/file
|
Go has several useful functions for working with directories in the file system. |
|
package main
|
|
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
|
|
func check(e error) {
if e != nil {
panic(e)
}
}
|
|
func main() {
|
|
Create a new sub-directory in the current working directory. |
err := os.Mkdir("subdir", 0755)
check(err)
|
When creating temporary directories, it's good
practice to |
defer os.RemoveAll("subdir")
|
Helper function to create a new empty file. |
createEmptyFile := func(name string) {
d := []byte("")
check(ioutil.WriteFile(name, d, 0644))
}
|
createEmptyFile("subdir/file1")
|
|
We can create a hierarchy of directories, including
parents with |
err = os.MkdirAll("subdir/parent/child", 0755)
check(err)
|
createEmptyFile("subdir/parent/file2")
createEmptyFile("subdir/parent/file3")
createEmptyFile("subdir/parent/child/file4")
|
|
|
c, err := ioutil.ReadDir("subdir/parent")
check(err)
|
fmt.Println("Listing subdir/parent")
for _, entry := range c {
fmt.Println(" ", entry.Name(), entry.IsDir())
}
|
|
|
err = os.Chdir("subdir/parent/child")
check(err)
|
Now we'll see the contents of |
c, err = ioutil.ReadDir(".")
check(err)
|
fmt.Println("Listing subdir/parent/child")
for _, entry := range c {
fmt.Println(" ", entry.Name(), entry.IsDir())
}
|
|
|
err = os.Chdir("../../..")
check(err)
|
We can also visit a directory recursively,
including all its sub-directories. |
fmt.Println("Visiting subdir")
err = filepath.Walk("subdir", visit)
}
|
|
func visit(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fmt.Println(" ", p, info.IsDir())
return nil
}
|
$ go run directories.go
Listing subdir/parent
child true
file2 false
file3 false
Listing subdir/parent/child
file4 false
Visiting subdir
subdir true
subdir/file1 false
subdir/parent true
subdir/parent/child true
subdir/parent/child/file4 false
subdir/parent/file2 false
subdir/parent/file3 false
|
Throughout program execution, we often want to create data that isn't needed after the program exits. Temporary files and directories are useful for this purpose since they don't pollute the file system over time. |
|
package main
|
|
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
|
|
func check(e error) {
if e != nil {
panic(e)
}
}
|
|
func main() {
|
|
The easiest way to create a temporary file is by
calling |
f, err := ioutil.TempFile("", "sample")
check(err)
|
Display the name of the temporary file. On
Unix-based OSes the directory will likely be |
fmt.Println("Temp file name:", f.Name())
|
Clean up the file after we're done. The OS is likely to clean up temporary files by itself after some time, but it's good practice to do this explicitly. |
defer os.Remove(f.Name())
|
We can write some data to the file. |
_, err = f.Write([]byte{1, 2, 3, 4})
check(err)
|
If we intend to write many temporary files, we may
prefer to create a temporary directory.
|
dname, err := ioutil.TempDir("", "sampledir")
fmt.Println("Temp dir name:", dname)
|
defer os.RemoveAll(dname)
|
|
Now we can synthesize temporary file names by prefixing them with our temporary directory. |
fname := filepath.Join(dname, "file1")
err = ioutil.WriteFile(fname, []byte{1, 2}, 0666)
check(err)
}
|
$ go run temporary-files-and-directories.go
Temp file name: /tmp/sample610887201
Temp dir name: /tmp/sampledir898854668
|
Unit testing is an important part of writing
principled Go programs. The |
|
For the sake of demonstration, this code is in package
|
package main
|
import (
"fmt"
"testing"
)
|
|
We'll be testing this simple implementation of an
integer minimum. Typically, the code we're testing
would be in a source file named something like
|
func IntMin(a, b int) int {
if a < b {
return a
} else {
return b
}
}
|
A test is created by writing a function with a name
beginning with |
func TestIntMinBasic(t *testing.T) {
ans := IntMin(2, -2)
if ans != -2 {
|
|
t.Errorf("IntMin(2, -2) = %d; want -2", ans)
}
}
|
Writing tests can be repetitive, so it's idiomatic to use a table-driven style, where test inputs and expected outputs are listed in a table and a single loop walks over them and performs the test logic. |
func TestIntMinTableDriven(t *testing.T) {
var tests = []struct {
a, b int
want int
}{
{0, 1, 0},
{1, 0, 0},
{2, -2, -2},
{0, -1, -1},
{-1, 0, -1},
}
|
t.Run enables running "subtests", one for each
table entry. These are shown separately
when executing |
for _, tt := range tests {
|
testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
t.Run(testname, func(t *testing.T) {
ans := IntMin(tt.a, tt.b)
if ans != tt.want {
t.Errorf("got %d, want %d", ans, tt.want)
}
})
}
}
|
Run all tests in the current project in verbose mode. |
$ go test -v
== RUN TestIntMinBasic
--- PASS: TestIntMinBasic (0.00s)
=== RUN TestIntMinTableDriven
=== RUN TestIntMinTableDriven/0,1
=== RUN TestIntMinTableDriven/1,0
=== RUN TestIntMinTableDriven/2,-2
=== RUN TestIntMinTableDriven/0,-1
=== RUN TestIntMinTableDriven/-1,0
--- PASS: TestIntMinTableDriven (0.00s)
--- PASS: TestIntMinTableDriven/0,1 (0.00s)
--- PASS: TestIntMinTableDriven/1,0 (0.00s)
--- PASS: TestIntMinTableDriven/2,-2 (0.00s)
--- PASS: TestIntMinTableDriven/0,-1 (0.00s)
--- PASS: TestIntMinTableDriven/-1,0 (0.00s)
PASS
ok examples/testing 0.023s
|
Command-line arguments
are a common way to parameterize execution of programs.
For example, |
|
package main
|
|
import (
"fmt"
"os"
)
|
|
func main() {
|
|
|
argsWithProg := os.Args
argsWithoutProg := os.Args[1:]
|
You can get individual args with normal indexing. |
arg := os.Args[3]
|
fmt.Println(argsWithProg)
fmt.Println(argsWithoutProg)
fmt.Println(arg)
}
|
To experiment with command-line arguments it's best to
build a binary with |
$ go build command-line-arguments.go
$ ./command-line-arguments a b c d
[./command-line-arguments a b c d]
[a b c d]
c
|
Next we'll look at more advanced command-line processing with flags. |
Command-line flags
are a common way to specify options for command-line
programs. For example, in |
|
package main
|
|
Go provides a |
import (
"flag"
"fmt"
)
|
func main() {
|
|
Basic flag declarations are available for string,
integer, and boolean options. Here we declare a
string flag |
wordPtr := flag.String("word", "foo", "a string")
|
This declares |
numbPtr := flag.Int("numb", 42, "an int")
boolPtr := flag.Bool("fork", false, "a bool")
|
It's also possible to declare an option that uses an existing var declared elsewhere in the program. Note that we need to pass in a pointer to the flag declaration function. |
var svar string
flag.StringVar(&svar, "svar", "bar", "a string var")
|
Once all flags are declared, call |
flag.Parse()
|
Here we'll just dump out the parsed options and
any trailing positional arguments. Note that we
need to dereference the pointers with e.g. |
fmt.Println("word:", *wordPtr)
fmt.Println("numb:", *numbPtr)
fmt.Println("fork:", *boolPtr)
fmt.Println("svar:", svar)
fmt.Println("tail:", flag.Args())
}
|
To experiment with the command-line flags program it's best to first compile it and then run the resulting binary directly. |
$ go build command-line-flags.go
|
Try out the built program by first giving it values for all flags. |
$ ./command-line-flags -word=opt -numb=7 -fork -svar=flag
word: opt
numb: 7
fork: true
svar: flag
tail: []
|
Note that if you omit flags they automatically take their default values. |
$ ./command-line-flags -word=opt
word: opt
numb: 42
fork: false
svar: bar
tail: []
|
Trailing positional arguments can be provided after any flags. |
$ ./command-line-flags -word=opt a1 a2 a3
word: opt
...
tail: [a1 a2 a3]
|
Note that the |
$ ./command-line-flags -word=opt a1 a2 a3 -numb=7
word: opt
numb: 42
fork: false
svar: bar
tail: [a1 a2 a3 -numb=7]
|
Use |
$ ./command-line-flags -h
Usage of ./command-line-flags:
-fork=false: a bool
-numb=42: an int
-svar="bar": a string var
-word="foo": a string
|
If you provide a flag that wasn't specified to the
|
$ ./command-line-flags -wat
flag provided but not defined: -wat
Usage of ./command-line-flags:
...
|
Some command-line tools, like the |
|
package main
|
|
import (
"flag"
"fmt"
"os"
)
|
|
func main() {
|
|
We declare a subcommand using the |
fooCmd := flag.NewFlagSet("foo", flag.ExitOnError)
fooEnable := fooCmd.Bool("enable", false, "enable")
fooName := fooCmd.String("name", "", "name")
|
For a different subcommand we can define different supported flags. |
barCmd := flag.NewFlagSet("bar", flag.ExitOnError)
barLevel := barCmd.Int("level", 0, "level")
|
The subcommand is expected as the first argument to the program. |
if len(os.Args) < 2 {
fmt.Println("expected 'foo' or 'bar' subcommands")
os.Exit(1)
}
|
Check which subcommand is invoked. |
switch os.Args[1] {
|
For every subcommand, we parse its own flags and have access to trailing positional arguments. |
case "foo":
fooCmd.Parse(os.Args[2:])
fmt.Println("subcommand 'foo'")
fmt.Println(" enable:", *fooEnable)
fmt.Println(" name:", *fooName)
fmt.Println(" tail:", fooCmd.Args())
case "bar":
barCmd.Parse(os.Args[2:])
fmt.Println("subcommand 'bar'")
fmt.Println(" level:", *barLevel)
fmt.Println(" tail:", barCmd.Args())
default:
fmt.Println("expected 'foo' or 'bar' subcommands")
os.Exit(1)
}
}
|
$ go build command-line-subcommands.go
|
|
First invoke the foo subcommand. |
$ ./command-line-subcommands foo -enable -name=joe a1 a2
subcommand 'foo'
enable: true
name: joe
tail: [a1 a2]
|
Now try bar. |
$ ./command-line-subcommands bar -level 8 a1
subcommand 'bar'
level: 8
tail: [a1]
|
But bar won't accept foo's flags. |
$ ./command-line-subcommands bar -enable a1
flag provided but not defined: -enable
Usage of bar:
-level int
level
|
Next we'll look at environment variables, another common way to parameterize programs. |
Environment variables are a universal mechanism for conveying configuration information to Unix programs. Let's look at how to set, get, and list environment variables. |
|
package main
|
|
import (
"fmt"
"os"
"strings"
)
|
|
func main() {
|
|
To set a key/value pair, use |
os.Setenv("FOO", "1")
fmt.Println("FOO:", os.Getenv("FOO"))
fmt.Println("BAR:", os.Getenv("BAR"))
|
Use |
fmt.Println()
for _, e := range os.Environ() {
pair := strings.SplitN(e, "=", 2)
fmt.Println(pair[0])
}
}
|
Running the program shows that we pick up the value
for |
$ go run environment-variables.go
FOO: 1
BAR:
|
The list of keys in the environment will depend on your particular machine. |
TERM_PROGRAM
PATH
SHELL
...
|
If we set |
$ BAR=2 go run environment-variables.go
FOO: 1
BAR: 2
...
|
The Go standard library comes with excellent support
for HTTP clients and servers in the |
package main
|
import (
"bufio"
"fmt"
"net/http"
)
|
|
func main() {
|
|
Issue an HTTP GET request to a server. |
resp, err := http.Get("http://gobyexample.com")
if err != nil {
panic(err)
}
defer resp.Body.Close()
|
Print the HTTP response status. |
fmt.Println("Response status:", resp.Status)
|
Print the first 5 lines of the response body. |
scanner := bufio.NewScanner(resp.Body)
for i := 0; scanner.Scan() && i < 5; i++ {
fmt.Println(scanner.Text())
}
|
if err := scanner.Err(); err != nil {
panic(err)
}
}
|
$ go run http-clients.go
Response status: 200 OK
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Go by Example</title>
|
Writing a basic HTTP server is easy using the
|
package main
|
import (
"fmt"
"net/http"
)
|
|
A fundamental concept in |
func hello(w http.ResponseWriter, req *http.Request) {
|
Functions serving as handlers take a
|
fmt.Fprintf(w, "hello\n")
}
|
func headers(w http.ResponseWriter, req *http.Request) {
|
|
This handler does something a little more sophisticated by reading all the HTTP request headers and echoing them into the response body. |
for name, headers := range req.Header {
for _, h := range headers {
fmt.Fprintf(w, "%v: %v\n", name, h)
}
}
}
|
func main() {
|
|
We register our handlers on server routes using the
|
http.HandleFunc("/hello", hello)
http.HandleFunc("/headers", headers)
|
Finally, we call the |
http.ListenAndServe(":8090", nil)
}
|
Run the server in the background. |
$ go run http-servers.go &
|
Access the |
$ curl localhost:8090/hello
hello
|
In the previous example we looked at setting up a simple
HTTP server. HTTP servers are useful for
demonstrating the usage of |
package main
|
import (
"fmt"
"net/http"
"time"
)
|
|
func hello(w http.ResponseWriter, req *http.Request) {
|
|
A |
ctx := req.Context()
fmt.Println("server: hello handler started")
defer fmt.Println("server: hello handler ended")
|
Wait for a few seconds before sending a reply to the
client. This could simulate some work the server is
doing. While working, keep an eye on the context's
|
select {
case <-time.After(10 * time.Second):
fmt.Fprintf(w, "hello\n")
case <-ctx.Done():
|
The context's |
err := ctx.Err()
fmt.Println("server:", err)
internalError := http.StatusInternalServerError
http.Error(w, err.Error(), internalError)
}
}
|
func main() {
|
|
As before, we register our handler on the "/hello" route, and start serving. |
http.HandleFunc("/hello", hello)
http.ListenAndServe(":8090", nil)
}
|
Run the server in the background. |
$ go run context-in-http-servers.go &
|
Simulate a client request to |
$ curl localhost:8090/hello
server: hello handler started
^C
server: context canceled
server: hello handler ended
|
Sometimes our Go programs need to spawn other, non-Go
processes. For example, the syntax highlighting on this
site is implemented
by spawning a |
|
package main
|
|
import (
"fmt"
"io/ioutil"
"os/exec"
)
|
|
func main() {
|
|
We'll start with a simple command that takes no
arguments or input and just prints something to
stdout. The |
dateCmd := exec.Command("date")
|
|
dateOut, err := dateCmd.Output()
if err != nil {
panic(err)
}
fmt.Println("> date")
fmt.Println(string(dateOut))
|
Next we'll look at a slightly more involved case
where we pipe data to the external process on its
|
grepCmd := exec.Command("grep", "hello")
|
Here we explicitly grab input/output pipes, start the process, write some input to it, read the resulting output, and finally wait for the process to exit. |
grepIn, _ := grepCmd.StdinPipe()
grepOut, _ := grepCmd.StdoutPipe()
grepCmd.Start()
grepIn.Write([]byte("hello grep\ngoodbye grep"))
grepIn.Close()
grepBytes, _ := ioutil.ReadAll(grepOut)
grepCmd.Wait()
|
We ommited error checks in the above example, but
you could use the usual |
fmt.Println("> grep hello")
fmt.Println(string(grepBytes))
|
Note that when spawning commands we need to
provide an explicitly delineated command and
argument array, vs. being able to just pass in one
command-line string. If you want to spawn a full
command with a string, you can use |
lsCmd := exec.Command("bash", "-c", "ls -a -l -h")
lsOut, err := lsCmd.Output()
if err != nil {
panic(err)
}
fmt.Println("> ls -a -l -h")
fmt.Println(string(lsOut))
}
|
The spawned programs return output that is the same as if we had run them directly from the command-line. |
$ go run spawning-processes.go
> date
Wed Oct 10 09:53:11 PDT 2012
|
> grep hello
hello grep
|
|
> ls -a -l -h
drwxr-xr-x 4 mark 136B Oct 3 16:29 .
drwxr-xr-x 91 mark 3.0K Oct 3 12:50 ..
-rw-r--r-- 1 mark 1.3K Oct 3 16:28 spawning-processes.go
|
In the previous example we looked at
spawning external processes. We
do this when we need an external process accessible to
a running Go process. Sometimes we just want to
completely replace the current Go process with another
(perhaps non-Go) one. To do this we'll use Go's
implementation of the classic
|
|
package main
|
|
import (
"os"
"os/exec"
"syscall"
)
|
|
func main() {
|
|
For our example we'll exec |
binary, lookErr := exec.LookPath("ls")
if lookErr != nil {
panic(lookErr)
}
|
|
args := []string{"ls", "-a", "-l", "-h"}
|
|
env := os.Environ()
|
Here's the actual |
execErr := syscall.Exec(binary, args, env)
if execErr != nil {
panic(execErr)
}
}
|
When we run our program it is replaced by |
$ go run execing-processes.go
total 16
drwxr-xr-x 4 mark 136B Oct 3 16:29 .
drwxr-xr-x 91 mark 3.0K Oct 3 12:50 ..
-rw-r--r-- 1 mark 1.3K Oct 3 16:28 execing-processes.go
|
Note that Go does not offer a classic Unix |
Sometimes we'd like our Go programs to intelligently
handle Unix signals.
For example, we might want a server to gracefully
shutdown when it receives a |
|
package main
|
|
import (
"fmt"
"os"
"os/signal"
"syscall"
)
|
|
func main() {
|
|
Go signal notification works by sending |
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
This goroutine executes a blocking receive for signals. When it gets one it'll print it out and then notify the program that it can finish. |
go func() {
sig := <-sigs
fmt.Println()
fmt.Println(sig)
done <- true
}()
|
The program will wait here until it gets the
expected signal (as indicated by the goroutine
above sending a value on |
fmt.Println("awaiting signal")
<-done
fmt.Println("exiting")
}
|
When we run this program it will block waiting for a
signal. By typing |
$ go run signals.go
awaiting signal
^C
interrupt
exiting
|
Use |
|
package main
|
|
import (
"fmt"
"os"
)
|
|
func main() {
|
|
|
defer fmt.Println("!")
|
Exit with status 3. |
os.Exit(3)
}
|
Note that unlike e.g. C, Go does not use an integer
return value from |
If you run |
$ go run exit.go
exit status 3
|
By building and executing a binary you can see the status in the terminal. |
$ go build exit.go
$ ./exit
$ echo $?
3
|
Note that the |