Basics
Packages
- Every program is made up of packages.
"math/rand"
package comprise files beginning withpackage rand
- Use parenthesized, “factored” import statement to group multiple packages.
- In a package, a name is exported if it begins with a capital letter, like
Pi
orPrintln
.- When importing a package, you can refer only to its exported names. Any “unexported” names are not accessible from outside the package.
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println(math.Pi)
}
Functions
- Type comes after the variable name.
- Shorten
x int, y int
to `x, y int. - A function can return any number of results.
- Go’s return values may be named. If so, they are treated as variables defined at the top of the function.
func add(x int, y int) int {
return x + y
}
// any number of results
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
// named return values
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
// naked return, should be avoid in long functions
return
}
Data types
- The expression
T(v)
converts the valuev
to the typeT
.
// basic
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
// represents a Unicode code point
float32 float64
complex64 complex128
g := 0.867 + 0.5i // complex128
// conversions
i := 42
f := float64(i)
u := uint(f)
Variables
var
statement can declare a list of variables.- Type is the last.
var
statement can be at package or function level.var
can have initializers.- Type can be omitted in this case.
:=
for short assignment.var
statement can be “factored” into blocks.- Variables declared without an explicit initial value are given their zero value.
0
for numeric types,false
for boolean types,""
empty string for strings
package main
import "fmt"
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
}
// initializer
var i, j int = 1, 2
var c, python, java = true, false, "no!"
// short assignment
k := 3
c, python, java := true, false, "no!"
// var block
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
Constants
- Constants are declared like variables, but with the const keyword.
- Constants can be character, string, boolean, or numeric values.
- Constants cannot be declared using the := syntax.
- Numeric constants are high-precision values.
- An untyped constant takes the type needed by its context.
const Pi = 3.14
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100 // works, longer than 64-bit
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
Control flows
For loops
- No parentheses around conditions.
- Braces
{}
are required. for
can be used aswhile
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
// init and inc conditions can be empty
for ; sum < 1000; {
sum += sum
}
// while
sum := 1
for sum < 1000 {
sum += sum
}
If
if
can start with a short statement to execute before the condition.- Variables declared here are only in scope until the end of
if
and related else.
- Variables declared here are only in scope until the end of
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("continue")
}
Switch
- Only run the selected case; thus
break
is not needed. - switch cases need not be constants or integers.
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
Defer
- A defer statement defers the execution of a function until the surrounding function returns.
- the function containing the defer statement
- Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order.
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
Advanced types
Pointers
*T
is a pointer to aT
value.- Its zero value is
nil
.
- Its zero value is
- No pointer arithmetic.
// declare a pointer
var p *int
// get pointer
i := 42
p = &i
// get the value
fmt.Println(*p)
// set the int value
*p = 21
Structs
struct
is a collection of fields.
// declare
type Vertex struct {
X int
Y int
}
// create an instance
v := Vertex{1, 2}
v2 := Vertex{X: 1} // 1, 0
v3 := Vertex{} // 0, 0
// set a field
v.X = 4
// get the pointer
p := &v
// set the field
(*p).X = 1
// or easier
p.X = 1
Arrays and slices
- Array cannot be resized.
[n]T
- Slice is dynamically sized, flexible view into the element in an array.
[]T
- Doesn’t store any data.
- Changing the elements of a slice modifies the corresponding elements of its underlying array.
- Zero value is
nil
. - Can be created with
make
- Can contain any type like other slices
func append(s []T, vs ...T) []T
- Append values into a slice
- A bigger array will be allocated if needed.
// declare
var a [2]string
primes := [6]int{2, 3, 5, 7, 11, 13}
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
// slices, exclude the last one
var s [] int = primes[1:4] // 1, 2, 3
// create a slice
q := []int{2, 3, 5, 7, 11, 13}
a := make([]int, 5) // len(a) = 5
a := make([]int, 5, 10) // len(a) = 5, capacity is 10
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
// omit high or low bounds
var a [10]int
a[0:10]
a[:10]
a[0:]
a[:]
// slice append
var s []int
s = append(s, 2, 3, 4)
Range
range
form of thefor
loop iterates over a slice or map.- For slice, two values for each iteration: index and element
- Skip one value by assigning
_
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
Maps
- A map maps keys to values.
- Zero value is
nil
.
m := make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
//
var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
}
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
m[key] = elem
elem = m[key]
delete(m, key)
// test if a key is present
// use assign symbol if needed
elem, is_exist := m[key] // is_exist: bool
Functions
- Functions are values too. They can be passed around just like other values.
- May be used as function arguments and return values.
- Go functions may be closures. A closure is a function value that references variables from outside its body.
- The function may access and assign to the referenced variables; in this sense the function is “bound” to the variables.
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
// closures
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
Methods and interfaces
Methods
- Go does not have classes; but we can define methods on types.
- A method is a function with a special receiver argument.
- Can only declare a method with a receiver whose type is defined in the same package as the method.
- Can declare methods with pointer receivers.
- Such methods can modify the value.
- Such methods can take either a value or pointer as the receiver.
- But functions can only take pointer as arguments.
- Methods with value argument can take either pointer or value.
- Functions must take values.
- Use a pointer receiver
- can modify the value.
- avoid copying the value on each call.
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
// equivalent to
fmt.Println(Abs(v))
}
// can define a new type and add methods
type MyFloat float64
// modify value
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
Interface
- An interface type is defined as a set of method signatures.
- A value of interface type can hold any value that implements those methods.
- No explicit declaration is needed, just implement those methods.
- Interface values stores the value and its concrete type.
describe(i)
- Call corresponding methods of the concrete type.
- Handle
nil
value in methods of a type. - Type switch
- Stringer
- Error
- Readers
- The
io
package specifies theio.Reader
interface, which represents the read end of a stream of data. - Many implementations for different cases.
err == io.EOF
when stream ends.
- The
type Abser interface {
Abs() float64
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
// Error in the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
a = v
fmt.Println(a.Abs())
}
// empty interface can hold values of any type
interface{}
// type assertion, access to an interface value's underlying concrete value.
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64) // zero value, false
fmt.Println(f, ok)
f = i.(float64) // panic
fmt.Println(f)
// type switch
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)
}
}
// Stringer interface, defined in fmt, can implement it to use Print
type Stringer interface {
String() string
}
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}
// Error interface built in, can fmt Print it
type error interface {
Error() string
}
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
// Reader
func (T) Read(b []byte) (n int, err error)
func read() {
r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}
Concurrency
Goroutines
go f(x, y, z)
starts a new running goroutine, a lightweight thread.- goroutines run in the same address space, so access to shared memory must be synchronized.
sync
package provides useful primitives.
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
go func() {
say("hello")
}()
}
Channels
- Channels are a typed conduit through which you can send and receive values with the channel operator,
<-
.- The data flows in the direction of the arrow.
- Channels must be created before use.
- By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
- Channels can be buffered. Provide the buffer length as the second argument to make to initialize a buffered channel.
- Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and assign value to v.
ch := make(chan int) // create a channel
// e.g.
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
// buffered channels
ch := make(chan int, 100)
- A sender can close a channel to indicate that no more values will be sent. Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression.
v, ok := <-ch
- Only sender can close the channel, never the receiver.
- The loop for
i := range c
receives values from the channel repeatedly until it is closed. - The
default
case in aselect
is run if no other case is ready.
Select
- The
select
statement lets a goroutine wait on multiple communication operations. - A
select
blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.
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)
}
// default
func main() {
tick := time.Tick(100 * time.Millisecond)
boom := time.After(500 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(50 * time.Millisecond)
}
}
}
Mutex
- Mutual exclusion: But what if we don’t need communication? What if we just want to make sure only one goroutine can access a variable at a time to avoid conflicts?
sync.Mutex
providesLock
andUnlock
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"))
}
Examples
Equivalent binary trees
package main
import "golang.org/x/tour/tree"
import "fmt"
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int, ) {
if (t == nil) {
return
}
Walk(t.Left, ch)
ch <- t.Value
Walk(t.Right, ch)
}
// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
Walk(t1, ch1)
close(ch1)
}()
go func() {
Walk(t2, ch2)
close(ch2)
}()
for {
v1, ok1 := <- ch1
v2, ok2 := <- ch2
if (ok1 != ok2) {
return false
}
if (!ok1) {
return true
}
if (v1 != v2) {
return false
}
}
}
func main() {
res := Same(tree.New(1), tree.New(2))
fmt.Println(res)
}