An Introduction to Programming in Go.
reading note of the golang book
- Chapter 1, Getting Started
- Chapter 2, Your First Program
- Chapter 3, Types
- Chapter 4, Variables
- Chapter 5, Control Structures
- Chapter 6, Arrays, Slices and Maps
- Chapter 7, Functions
- Chapter 8, Pointers
- Chapter 9, Structs and Interfaces
- Chapter 10, Concu rrency
- Chapter 11, Packages
- Chapter 12, Testing
- Chapter 13, The Core Packages
- Chapter 14, Next Steps
Chapter 1, Getting Started
(no notes)
Chapter 2, Your First Program
There are two types of Go programs: executables and libraries.
Chapter 3, Types
Numbers
Integers
Go's integer types are: uint8
, uint16
, uint32
, uint64
, int8
, int16
, int32
and int64
.
uint
means unsigned integer while int
means signed integer
In addition there two alias types:
byte
is the same asuint8
rune
is the same asint32
Floating Point Numbers
- Floating point numbers are inexact.
- Like integers floating point numbers have a certain size (32 bit or 64 bit). Using a larger sized floating point number increases it's precision.
- In addition to numbers there are several other values which can be represented: "not a number" (
NAN
) and positive and negative infinity.
Go has two floating point types:
float32
andfloat64
complex64
andcomplex128
Strings
Booleans
Chapter 4, Variabl es
Variables in Go are created by first using the var
keyword, then specifying the variable name (x
), the type (string
) and finally assigning a value to the variables.
var x string = "Hello"
Since creating a new variable with a sharting value is so common Go also supports a shorter statement:
x := "Hello"
The type is not necessary because the Go compiler is able to infer the type based on the literal value you assign the value.
How to Name a Variable
Names must start with a letter.
Scope
Go is lexically scoped using blocks. means that the variable exists within the nearest curly braces { }
(a block) including any nested curly braces (blocks), but not outside of them.
Constants
Constants are basically variables whose values cannot be changed later. they are created in the same way you create variables but instead of using the var
keyword we use the const
keyword.
Defining Multiple Variables
var (
a = 5
b = 10
c = 15
)
use the key
word var
(or const
) followed by parentheses with each variable on his own line.
Chapter 5, Control Structures
For
i := 1
for i <= 10 {
fmt.Println(i)
i+=1
}
Other programmi
ng languages have a lot of different types of loops (while, do, until, foreach, ...) but Go only has one that can be used in a variety of different ways:
for i := 1; i <= 10; i++ {
fmt.Println(i)
}
If
if cond {
// ...
} else if cond {
// ...
} else {
// ...
}
Switch
switch i {
case 0: fmt.Println("zero")
case 1: fmt.Println("one")
default: fmt.Println("unknown")
}
Chapter 6, Arrays, Slices and Maps
Arrays
var x [5]int
for _, value := range x {
total += value
}
A single _
(underscore) is used to tell the compiler that we don't need this.
Go also provides a shorter syntax for creating arrays:
x := [5]float64{ 98, 93, 77, 82, 83 }
Slices
A slice is a segment of an array.
Like arrays slices are indexable and have a length. Unlike arrays this length is allowed to change.
var x []float64
the length is missing between the brackets, x
has been created with a length of 0
.
but you should create a slice using the built-in make
function:
x := make([]float64, 5)
This creates a slices that is associated with an underlying float64
array of length 5
.
x := make([]float64, 5, 10)
10
represents the capacity of the underlying array which the slice points to.
another way to create slices is to use the [low : high]
expression:
arr := []float64{1,2,3,4,5}
x := arr[0:5]
low
is the index of where to start the slice high
is the inde x where to end it (but not including the index itself)
for convenience we are allowed to omit low
, high
or even both.
Slice Functions
Go includes two built-in functions to assist with slices: append
and copy
func main() {
slice1 := []int{1,2,3}
slide2 := append(slice1, 4, 5)
fmt.Println(slice1, slice2)
}
append
creates a new slice by taking an existing slice and appending all the following arguments to it.
func main() {
slice1 := []int{1,2,3}
slide2 := make([]int, 2)
copy(slice2, slice1)
fmt.Println(slice1, slice2)
}
the content of slice1
are copied to slice2
(slice2
has room for only two elements, only the first two elements are copied)
Maps
A map is an unordered collection of key-value pairs.
var x map[string]int
key type in brackets and then value type
to initialize a map:
x := make(map[string]int)
x["key"] = 10
fmt.Println(x["key"])
delete items from a map:
delete(x, "key")
accessing an element of a map can return two values: first is the result of the lookup, second is whether or not the lookup was successful
if name, ok := ele ments["un"]; ok {
fmt.Println(name, ok)
}
first we try to get the value from the map, then if it's successful we run the code inside of the block.
another way to create map
elements := map[string]string {
"H": "Hydrogen",
...
"Ne": "Neon",
}
(the last ,
is required)
multi-dimention map
var elements = map[string]map[string]string
Chapter 7, Functions
func name(x type, y type, ..) return_type { }
the parameters and the return type are known as the function's signature.
we can also name the return type:
func f2() (r int) {
r = 1
return
}
Returning Multiple Values
func f() (int, int) {
return 5, 6
}
func main() {
x, y := f()
}
multiple values are often used to return an error value along with the result (x, err := f()
), or a boolean to indicate success (x, ok := f()
)
Variadic Functions
func add(args ...int) int {
total := 0
f or _, v := range args {
total += v
}
return total
}
func main() {
fmt.Println(add(1,2,3))
}
by using ...
before the type name of the last parameter you can indicate that it takes zero of more of those parameters.
we can also pass a slice of int
s by following the slice with ...
:
func main() {
xs := []in
t{1,2,3}
fmt.Println(add(xs...))
}
Closure
create functions inside of functions
func main() {
add := func(x, y int) int {
return x + y
}
fmt.Println(add(1,1))
}
when you create a local function like this it also has access to other local variables .
func main() {
x := 0
increment := func() int {
x++
return x
}
fmt.Println(increment())
fmt.Println(increment())
}
A function like this together with the non-local variables it references is known as a closure.
Recursion
func factorial(x uint) uint {
if x == 0 {
return 1
}
return x * factorial(x-1)
}
Defer, Panic & Recover
Go has a special statement called defer
which schedules a function call to be run after the function completes.
defer
is often used when resources need to be freed in some way.
f, _ := os.Open(filename)
defer f.Close()
this has 3 advantages:
- it keeps our
Close
call near ourOpen
call so its easier to understand. - if our function had multiple return statements (perhaps one in an
if
and one in anelse
)Close
will happen before both of them. - deferred functions are run even if a run-time panic occurs.
call the panic
function to cause a run time error. and handle a run-time panic with the built-in recover
function.
recover
stops the panic and returns the value that was passed to the call to panic
.
and recover
usually pair with defer
(because panic
stops execution, recover
won't be able to happen):
package main
import "fmt"
func main() {
defer func() {
str := recover()
fmt.Println(str)
}()
panic("PANIC")
}
Chapter 8, Pointers
when we call a fnction that takes an argument, that argument is copied to the function. use pointer if we need to modify the origin variable.
func zero(xPtr *int) {
*xPtr = 0
}
fun
c main() {
x := 5
zero(&x)
fmt.Println(x) // x is 0
}
points reference a location in memory where a value is stored rather than the value itself.
The * and & operators
in Go a pointer is represented using the *
(asterisk) followed by the type of the stored value.
*
is also used to dereference pointer variables. dereferencing a pointer gives us access to the value the pointer points to.
finally we use the &
operator to find the address of a variable.
new
another way to get a pointer is to use the built-in new
function:
func one(xPtr *int) {
*xPtr = 1
}
func main() {
xPtr := new(int)
one(xPtr)
fmt.Println(*xPtr) // x is 1
}
new
takes a type as an argument, allocates enough memory to fit a value of that type and returns a pointer to it.
pointers are rarely used with Go's built-in types, but they are extremely useful when paired with structs
Chapter 9, Structs and Interfaces
Structs
A struct is a type which contains named fiel ds.
type Circle struct {
x, y, r float64
}
Initialization
var c Circle
this will create a local Circle
variable that is by default set to zero.
zero means e ach of the fields is set to their coresponding zero value (0
for int
s, 0.0
for float
s, ""
for string
s, nil
for points, ...)
we can also use the new
function:
c := new(Circle)
this allocates memory for all the fields, sets each of them to their zero value and return a pointer (*Circle
)
to give value on initialization:
c := Circle{x: 0, y: 0, r: 5}
or we can leave off the field names if we know the order they were defined:
c := Circle{0, 0, 5}
*Field
s*
we can access fields using the .
operator
c.x = 10
c.y = 5
fmt.Println(c.x, c.y, c.r)
Methods
method is a special type of function
func (c *Circle) area() float64 {
reutrn math.Pi * c.r * c.r
}
in between the keyword func
and the name of the function we've added a receiver.
the receiver is like a parameter - it has a name and a type - but by creating the function in this way it allows us to call the function using the .
operator:
fmt.Println(c.area())
we no longer need the &
operator (Go automatically knows to pass a pointer to the circle for this method)
Embedded Types
A s
truct's field usually represent the has-a relationship.
Embedded type also known as anonymous fields:
type Android struct {
Person
Model string
}
a := new(Android)
a.Person.Talk()
we use the type Person
and don't give it a name.
but we can also call any Person
methods directly on the Android
:
a := new(Android)
a.Talk()
Interfaces
type Shape interface {
area() float64
}
like a struct an interface is created using the type
keyword, followed by a name and the keyword interface
.
but instead of defining fields, we define a method set. a method set is a list of methods that a type must have in order to implement the interface.
interface can also be used as fields:
type MultiShape struct {
shapes []Shape
}
Chapter 10, Concurrency
Goroutines
A goroutine is a function that is capable of running concurrently with other functions.
to create a goroutine we use the keyword go
followed by a function invocation:
package main
import "fmt"
func f(n int) {
for i := 0; i < 10; i++ {
fmt.Println(n, ":", i)
}
}
func main() {
go f(0)
var input string
fmt.Scanln(&input)
}
This program consists of two goroutines. The first goroutine is implicit and is the main function itself. The second goroutine is created when we call go f(0)
.
With a goroutine we return immediately to the next line and don't wait for the function to complete.
Goroutines are lightweight and we can easily create thousands of them.
Channels
Channels provide a way for two goroutines to communicate with one another and synchronize their execution.
package main
import (
"fmt"
"time"
)
func pinger(c chan string) {
for i := 0; ; i++ {
c <- "ping"
}
}
func printer(c chan string) {
for {
msg := <- c
fmt.Println(msg)
time.Sleep(time.Second * 1)
}
}
func main() {
var c chan string = make(chan string)
go pinger(c)
go printer(c)
var input string
fmt.Scanln(&input)
}
A channel type is represented with the keyword chan
followed by the type of the things that are passed on the channel.
The <-
(left arrow) operator is used to send and receive messages on the channel.
c <- "ping"
means send "ping"
. msg := <- c
means receive a message and store it in msg
.
Channel Direction
We can specify a direction on a channel type thus restricting it to either sending or receiving.
func pinger(c chan<- string)
c
can only be sent to. Attempting to receive from c
will result in a compiler error.
func printer(c <-chan string)
A channel that doesn't have these restrictions is known as bi-directional.
A bi-directional channel can be passed to a function that takes send-only or receive-only channels, but the reverse is not true.
Select
Go has a special statement called select
which works like a switch
but for channels:
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
for {
c1 <- "from 1"
time.Sleep(time.Second * 2)
}
}()
go func() {
for {
c2 <- "from 2"
time.Sleep(time.Second * 3)
}
}()
go func() {
for {
select {
case msg1 := <- c1:
fmt.Println(msg1)
case msg2 := <- c2:
fmt.Println(msg2)
}
}
}()
var input string
fmt.Scanln(&input)
}
The select
statement is often used to implement a timeout:
select {
case msg1 := <- c1:
fmt.Println("Message 1", msg1)
case msg2 := <- c2:
fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
fmt.Println("timeout")
default:
fmt.Println("nothing ready")
}
The default
case happens immediately if none of the channels are ready.
Buffered Channels
c := make(chan int, 1)
This creates a buffered channel with a capacity of 1.
Normally channels are synchronous; both sides of the channel will wait until the other side is ready.
A buffered channel is asynchronous; sending or receiving a message will not wait unless the channel is already full.
Chapter 11, Packages
Creating Packages
run go install
to compile package.
Go packages can be hierarchical.
alias:
import m "foo/bar/math"
In Go if something starts with a capital letter that means other packages (and programs) are able to see it.
Package names match the folders they fall in.
Documentation
godoc foo/bar/math Average
We can improve this documentation by adding a comment before the function:
// Finds the average of a series of numbers
func Average(xs []float64) float64 {}
doc http server
godoc -http=:3000
Chapter 12, Testing
package math
import "testing"
func TestAverage(t *testing.T) {
var v float64
v = Average([]float64{1,2})
if v != 1.5 {
t.Error("Expected 1.5, got ", v)
}
}
run go test
, it will look for any tests in any of the files in the current folder and run them.
Tests are identified by starting a function with the word Test
and taking one argument of type *testing.T
.
Chapter 13, The Core Packages
check https://github.com/usrjim/goplay
Chapter 14, Next Steps
(no notes)
[END]