Golang - ttulka/programming GitHub Wiki
Syntax
Packages
Every Go program is made up of packages.
- Reusable: code used as "helpers".
- Executable: generates a runnable file
- Programs start running in package
main
. - Must have a func
main()
- Programs start running in package
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
A name is exported if it begins with a capital letter.
Functions
A function can take zero or more arguments.
func add(x int, y int) int {
return x + y
}
// or add(x, y int)
A function can return 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)
} // prints world hello
Return values may be named:
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
} // prints 7 10
Functions are values too. They can be passed around just like other values.
- may be used as function arguments and return values.
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12)) // 13
fmt.Println(compute(hypot)) // 5
fmt.Println(compute(math.Pow)) // 81
}
The closures functions may access and assign to the referenced variables:
func fibonacci() func() int {
m := 0
n := 1
return func() int {
tmp := m
m = n
n += tmp
return tmp
} }
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
} }
Variables
The var
statement declares a list of variables; as in function argument lists, the type is last.
- can be at package or function level.
var c, python, java bool
func main() {
var i int
fmt.Println(i, c, python, java)
} // prints 0 false false false
A var
declaration can include initializers, one per variable:
var i, j int = 1, 2
func main() {
var c, python, java = true, false, "no!"
fmt.Println(i, j, c, python, java)
}
- If an initializer is present, the type can be omitted.
Inside a function, the :=
short assignment statement can be used in place of a var declaration with implicit type:
func main() {
i := 3
fmt.Println(i)
} // prints 3
- Outside a function the
:=
construct is not available.
Redeclaration and reassignment
f, err := os.Open(name)
d, err := f.Stat() // err is re-assigned
Basic types
bool
string
int
,int8
,int16
,int32
,int64
uint
,uint8
,uint16
,uint32
,uint64
,uintptr
byte
- alias foruint8
rune
- alias forint32
, represents a Unicode code pointfloat32
,float64
complex64
,complex128
The int
, uint
, and uintptr
types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems.
The expression T(v)
converts the value v
to the type T
:
i := 42
f := float64(i)
u := uint(f)
b := []byte("string")
When declaring a variable without specifying an explicit type the variable's type is inferred from the value on the right hand side:
var i int
j := i // j is an int
When the right hand side contains an untyped numeric constant, the new variable type depends on the precision of the constant:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
Constants expression is repeated by the other constants until another assignment or type declaration shows up.
- cannot be declared using the
:=
syntax. const World = "Welt"
Iota are declared like variables, but with the const
keyword.
type Day int
const (
Monday Day = iota + 1
Tuesday
Wednesday
...
)
Zero values
Variables declared without an explicit initial value are given their zero value.
0
for numeric types,false
for the boolean type, and""
(the empty string) for strings.nil
for pointers, slices, maps
For
Go has only one looping construct, the for
loop.
- init statement: executed before the first iteration
- condition expression: evaluated before every iteration
- post statement: executed at the end of every iteration
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
The init and post statements are optional:
sum := 1
for ; sum < 1000; { // for sum < 1000 {
sum += sum
}
fmt.Println(sum)
If the loop condition is omitted it loops forever: for {}
If
if
statement can start with a short statement to execute before the condition:
if v := math.Pow(x, n); v < lim {
// do something
} else {
// do else
}
Switch
Go only runs the selected case, not all the cases that follow.
- break statement that is needed at the end of each case.
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd, plan9, windows...
fmt.Printf("%s.\n", os)
}
Evaluates cases from top to bottom, stopping when a case succeeds.
switch i {
case 0:
case f(): // does not call `f` if `i==0`
}
Switch without a condition is the same as switch true
.
- can be a clean way to write long if-then-else chains.
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
Switch can have multiple case options:
switch protocol {
case "tcp", "tcp4", "tcp6", "unix": // all is fine
default:
fmt.Println("Unsupported protocol:", protocol)
os.Exit(2)
}
Pointers
A pointer holds the memory address of a value.
- The type
*T
is a pointer to aT
value. - The
&
operator generates a pointer to its operand. - The
*
operator denotes the pointer's underlying value.
var p *int
i := 42
p = &i
fmt.Println(*p) // read i through the pointer p
*p = 21 // set i through the pointer p
type person struct {
name string
}
func (p person) updateName1(name string) {
p.name = name
}
func (p *person) updateName2(name string) {
(*p).name = name
}
func (p *person) updateName3(name string) {
p.name = name
}
func main() {
jim := person{"Jim"}
jim.updateName1("Jimmy1");
fmt.Println(jim) // no change, passed by value
(&jim).updateName2("Jimmy2A");
fmt.Println(jim)
jim.updateName2("Jimmy2B");
fmt.Println(jim)
(&jim).updateName3("Jimmy3A");
fmt.Println(jim)
jim.updateName3("Jimmy3B");
fmt.Println(jim)
}
func updateString(s *string, v string) {
*s = v
}
func main() {
s := "abc"
updateString(&s, "def")
fmt.Println(s) // def
}
Value types need using pointers to change the value in a function:
int
,float
,string
,bool
- structs
- arrays
Reference types don't need explicitly using pointers:
- slices
- maps
- channels
- functions
- pointers
Everything in Go is pass by value:
func main() {
s := "abc"
p := &s
fmt.Println(p) // 0xc000044240
fmt.Println(&p) // 0xc0000a0020
printPointer(p) // 0xc0000a0028
}
func printPointer(p *string) {
fmt.Println(&p)
}
Structs
A struct
is a collection of fields.
- Struct fields are accessed using a dot.
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X) // 4
}
Struct fields can be accessed through a struct pointer:
v := Vertex{1, 2}
p := &v
p.X = 1e5
fmt.Println(v) // {100000 2}
A *struct literal denotes a newly allocated struct value by listing the values of its fields:
v1 := Vertex{1, 2} // has type Vertex
v2 := Vertex{X: 1} // Y:0 is implicit
v3 := Vertex{} // X:0 and Y:0
p := &Vertex{1, 2} // has type *Vertex
Embedding structs
type ReaderData struct {
Read int
}
type WriterData struct {
Written int
}
type ProcessorData struct {
*ReaderData
*WriterData
}
func main() {
proc := ProcessorData{&ReaderData{123}, &WriterData{456}}
fmt.Println(proc.Read, proc.Written) // 123 456
}
Arrays
The type [n]T
is an array of n values of type T
.
var a [10]int
declares a variablea
as an array of ten integers.- arrays cannot be resized.
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1]) // Hello World
fmt.Println(a) // [Hello World]
primes := [5]int{2, 3, 5, 7, 11}
fmt.Println(primes) // [2 3 5 7 11]
The type []T
is a slice with elements of type T
.
- A slice is a dynamically-sized, flexible view into the elements of an array.
- A slice is formed by specifying two indices, a low and high bound, separated by a colon:
a[low : high]
- includes the first element, but excludes the last one.
- does not store any data, it just describes a section of an underlying array.
[]int{1, 2, 3}
creates an array, then builds a slice that references it.
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s) // [3 5 7]
s[0] = 999
fmt.Println(s) // [999 5 7]
fmt.Println(primes) // [2 999 5 7 11 13]
High or low bounds can be omitted to use their defaults instead:
var a [10]int
// these are equivalent:
a[0:10]
a[:10]
a[0:]
a[:]
For a slice, Go will automatically create which two data structures:
- an array,
- a structure that records the length of the slice, the capacity of the slice, and a reference to the underlying array.
A slice has both a length and a capacity.
- The length of a slice is the number of elements it contains.
- The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.
s := []int{1, 2, 3, 4, 5, 6} // len(s)=6 cap(s)=6
s = s[2:4] // len(s)=2 cap(s)=4
Slices can be created with the built-in make
function:
a := make([]int, 5) // len(a)=5
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
Slices can contain any type, including other slices:
board := [][]string{
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
[]string{"_", "_", "_"},
}
board[0][0] = "X"
board[2][2] = "O"
Slice values are not passed by value:
- The slice is passed by value, but it still pointing to the original underlying array.
- Slices are reference types.
func updateSlice(a []int, v int) {
a[0] = v
}
func main() {
a := []int{1, 2, 3}
updateSlice(a, 999)
fmt.Println(a) // [999 2 3]
}
Built-in append
function appends new elements to a slice: func append(s []T, vs ...T) []T
- The first parameter
s
ofappend
is a slice of typeT
, and the rest areT
values to append to the slice. - The resulting value of append is a slice containing all the elements of the original slice plus the provided values.
s := []int{1, 2, 3} // len=3 cap=3 [1 2 3]
s = append(s, 88, 99) // len=5 cap=8 [1 2 3 88 99]
The range
form of the for
loop iterates over a slice or map.
- Two values are returned for each iteration: 1) index, 2) copy of the element at that index
pows := []int{2, 4, 8}
for i, v := range pows {
fmt.Printf("%d=%d\n", i, v)
} // 0=2, 1=4, 2=8
You can skip the index or value by assigning to _
:
for i, _ := range pow
for _, v := range pow
for i := range pow // only index
new
vs make
new
Allocation with new
, a built-in function allocates memory, but it does not initialize the memory, it only zeros it.
new(T)
allocates zeroed storage for a new item of typeT
and returns its address, a value of type*T
.
make
Allocation with make(T, args)
, a built-in function creates slices, maps, and channels only, and it returns an initialized (not zeroed) value of type T
(not *T
).
- these three data structures must be initialized before use.
var p *[]int = new([]int) // allocates slice structure; *p == nil; rarely useful
var v []int = make([]int, 100) // the slice v now refers to a new array of 100 ints
// Unnecessarily complex:
var p *[]int = new([]int)
*p = make([]int, 100, 100)
// Idiomatic:
v := make([]int, 100)
Maps
A map maps keys to values.
m := make(map[string]int)
m["First"] = 1
m["Second"] = 2
// or
m := map[string]int{
"First": 1,
"Second": 2,
}
fmt.Println(m, m["First"], m["Second"])
// map[First:1 Second:2] 1 2
delete(m, "Second")
second, ok := m["Second"]
fmt.Println(m, ok, second)
// map[First:1] false 0
for k, v := range m {
fmt.Println("Key", k, "has value", v)
}
Errors
Go programs express error state with error
values.
- The
error
type is a built-in interface:
type error interface {
Error() string
}
Defer, Panic, and Recover
Defer statement pushes a function call onto a list.
- A deferred function's arguments are evaluated when the defer statement is evaluated.
- Deferred function calls are executed in Last In First Out order after the surrounding function returns.
- Deferred functions may read and assign to the returning function's named return values.
// returns 2
func c() (i int) {
defer func() { i++ }()
return 1
}
Since defer will always get executed when the surrounding function returns, it is a good place to attach cleanup code such as:
- Closing open files
- Releasing network resources
- Closing the Go channel
- Committing database transactions
file, err := os.Open("data.txt")
if err != nil {
return nil, err
}
defer file.Close()
Panic is a built-in function that stops the ordinary flow of control and begins panicking.
panic
, a built-in function in effect creates a run-time error that will stop the program.- takes a single argument of arbitrary type—often a string—to be printed as the program dies.
func init() {
if os.Getenv("USER") == "" {
panic("no value for $USER")
}
}
Recover is a built-in function that regains control of a panicking goroutine.
func main() {
f()
fmt.Println("Returned normally from f.")
}
func f() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f, cause:", r)
}
}()
fmt.Println("Calling g.")
g(0)
fmt.Println("Returned normally from g.")
}
func g(i int) {
if i > 3 {
fmt.Printf("Panicking with %v!\n", i)
panic(fmt.Sprintf("Wrong value (%v)", i))
}
defer fmt.Println("Defer in g", i)
fmt.Println("Printing in g", i)
g(i + 1)
}
Methods
Method is just a function with a receiver argument defined on types.
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())
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
Methods which need to modify their receiver are using pointer receivers:
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
- More efficient as it doesn't copy the value on each method call.
Interfaces
An interface type is defined as a set of method signatures.
- Calling a method on an interface value executes the method of the same name on its underlying type.
- If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
- it is common to write methods that gracefully handle being called with a nil receiver (
if t == nil
)
- it is common to write methods that gracefully handle being called with a nil receiver (
type Abser interface {
Abs() float64
}
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)
a = f // a MyFloat implements Abser
fmt.Println(a.Abs()) // 1.4142135623730951
}
Assembling interface brings multiple interfaces together to create another interface.
type ReadCloser interface {
Reader
Closer
}
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
The interface type that specifies zero methods is known as the empty interface: interface{}
.
- An empty interface may hold values of any type.
- Empty interfaces are used by code that handles values of unknown type.
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
func main() {
var i interface{}
describe(i)
i = 42
describe(i)
i = "hello"
describe(i)
}
A type assertion provides access to an interface value's underlying concrete value.
- Asserts that the interface value
i
holds the concrete typeT
and assigns the underlyingT
value to the variablet
. - If
i
does not hold aT
, the statement will trigger a panic. t := i.(T)
ort, ok := i.(T)
var i interface{} = "hello"
s := i.(string)
fmt.Println(s) // hello
s, ok := i.(string)
fmt.Println(s, ok) // hello true
f, ok := i.(float64)
fmt.Println(f, ok) // 0 false
f = i.(float64) // panic
Stringer
Stringer
is one of the most ubiquitous interfaces defined by the fmt
package.
type Stringer interface {
String() string
}
A Stringer is a type that can describe itself as a string.
- The fmt package (and many others) look for this interface to print values.
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
Embedding
Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// Processor can do what Reader does and what Writer does
type Processor interface {
Reader
Writer
}
Goroutines
A goroutine is a lightweight thread managed by the Go runtime.
go f(x, y, z)
starts a new goroutine runningf(x, y, z)
.- Goroutines run in the same address space, so access to shared memory must be synchronized.
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")
} // prints world hello hello world world hello ...
We can use function literals in goroutines:
go func() {
time.Sleep(5 * time.Second)
doSomethingConcurrently()
}()
A variable should be passed as a parameter, rather than reading it from a closure:
starts := []int{10,40,70,100}
for _, j := range starts{
go func(s int) {
count(s, s + 20, 10)
}(j)
}
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.
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)
} // prints 0 1 1 2 3 5 8 13 21 34 quit
The default
case in is run if no other case is ready.
select {
case i := <-c:
// use i
default:
fmt.Println("Channel not ready yet...")
time.Sleep(50 * time.Millisecond)
}
Channels
Channels are a typed conduit through which you can send and receive values with the channel operator <-
.
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and assign value to v.
- The data flows in the direction of the arrow.
- Channels must be created before use:
ch := make(chan int)
- By default, sends and receives block until the other side is ready.
- Can be declared as send- or receive-only: func doSmt(in chan<- int, out <-chan int) { }`
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) // -5 17 12
}
Channels can be buffered. Provide the buffer length: ch := make(chan int, 128)
- Sends to a buffered channel block only when the buffer is full.
- Receives block when the buffer is empty.
Sending to an unbuffered channel can easily cause a deadlock if the operation is not wrapped in a goroutine:
ch := make(chan int)
ch <- 12 // deadlock
fmt.Println(<-ch)
- Fix by using a buffered channel:
ch := make(chan int, 2)
- Fix by using a goroutine:
go func() { ch <- 1 }()
Sender can close the channel to indicate that no more values will be sent.
- Receivers can test whether a channel has been closed by assigning a second parameter
v, ok := <-ch
- The loop
for i := range c
receives values from the channel repeatedly until it is closed.
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
} // prints 0 1 1 2 3 5 8 13 21 34
sync.Mutex
Go's standard library provides mutual exclusion with sync.Mutex and its methods: Lock
and Unlock
.
var mux sync.Mutex
mux.Lock()
// Lock so only one goroutine at a time can access the map c.v.
value++
mux.Unlock()
Printing
// each line produces the same output
fmt.Printf("Hello %d\n", 23)
fmt.Fprint(os.Stdout, "Hello ", 23, "\n")
fmt.Println("Hello", 23)
fmt.Println(fmt.Sprint("Hello ", 23))
fmt.Println(name, `says "Hello!"`)
Wait for a value to be sent into a channel, then print it immediately:
fmt.Println(<- channel)
Organization
- Keep all Go code in a single workspace.
- A workspace contains many version control repositories.
- Each repository contains one or more packages.
- Each package consists of one or more Go source files in a single directory.
- The path to a package's directory determines its import path.
Workspaces
A workspace is a directory hierarchy with two directories at its root:
src
contains Go source files, andbin
contains executable commands.
bin
└─hello
src
└─github.com
└─ttulka
└─golang-samples
├─hello
│ └─hello.go
└─stringutil
└─stringutil.go
GOPATH
The GOPATH
environment variable specifies the location of your workspace.
go env GOPATH
GOBIN
The GOBIN
environment variable specifies the location where binaries are installed.
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN
GOROOT
The GOROOT
environment variable specifies the Go SDK location.
export GOROOT=$HOME/go
export PATH=$PATH:$GOROOT/bin
GOOS
The GOOS
environment variable specifies the target operation system for which to compile code.
linux
,darwin
,windows
, ...
GOOS=windows GOARCH=amd64 go install
GOARCH
The GOARCH
environment variable specifies the architecture, or processor, for which to compile code.
amd64
,386
,arm
,ppc64
, ...
Install a program
go install $GOPATH/src/github.com/ttulka/golang-samples/hello
$GOPATH/bin/hello
Build a library
go build $GOPATH/src/github.com/ttulka/golang-samples/stringutil
import (
"fmt"
"github.com/ttulka/golang-samples/stringutil"
)
Run a program from source code
go run $GOPATH/src/github.com/ttulka/golang-samples/hello/hello.go
Download a remote source code
go get -u github.com/gorilla/mux
Testing
Go has a lightweight test framework composed of the go test
command and the testing
package.
go test $GOPATH/src/github.com/ttulka/golang-samples/stringutil
go test -cover // with coverage
import "testing"
func TestReverse(t *testing.T) {
got := reverse("abc")
if got != "cba" {
t.Errorf("Expected 'cba', but got '%v'", got)
}
}
Formatting
// a file or all files direct in the folder
go fmt $GOPATH/src/github.com/ttulka/golang-samples/hello/hello.go
go fmt $GOPATH/src/github.com/ttulka/golang-samples/hello
// all files recursively
gofmt $GOPATH/src/github.com/ttulka/golang-samples
References
- Language Specification: https://golang.org/ref/spec
- Packages: https://golang.org/pkg
- Testing: https://golang.org/pkg/testing
- Playground: https://play.golang.org
- How to Code: https://golang.org/doc/code.html
- Go Tour: https://tour.golang.org
- Effective Go: https://golang.org/doc/effective_go.html
- Testing in Go
- From Java to Go
- Go compiler for microcontrollers and WebAssembly: https://tinygo.org
- 12 Factor CLI Apps
- Building Microservices with Go
- GoPacket Tutorial
- Build TCP in Go
- Clean architecture in Go