What is gobook?
Gobok started as an extension to run Go interactivly in VS Code and save results to markdown ready for uploading to a blog or documentation sites. This site is built in combination with gobook and a Rust static site generator, it's thrown together with personal notes I took as I was learning Go, but over time will be refined to something similar to the Rust Book
books := []string{"gobook", "mdbook"}
fmt.Printf("This site was built with %s and %s", books[0], books[1])
This site was built with gobook and mdbook
Installing gobook
This will allow you to start playing with go in the fastest way possible
Install software
Test extension
- Create a new file called test.md
- If the extension doesn't active right click test.md and select reopen with, then Gobook:
- Add a code cell
- Run some code
- ctrl + click the file name in terminal to view generated code
- Upload test.md to Github for a niceley rendered markdown document, with outputs retained
Basics
Variables
Declare and initialize
var a int = 10
Inferred type
var b = 10
fmt.Println(reflect.TypeOf(b))
int
Initialized to 0
var c int
fmt.Println(c)
0
Multi initializer
var d, e int = 10, 20
Inferred multi
var f, g = 10, "wow"
fmt.Printf("%v\n%v",reflect.TypeOf(f), reflect.TypeOf(g))
int
string
Multiline initializer
var (
h int
i = 20
j int = 30
k, l = 40, "hello"
m, n string
)
fmt.Println(h, i, j, k, l, m, n)
0 20 30 40 hello
Shorthand initializer
o := 10
p, q := 30, "hello"
fmt.Println(o, p, q)
10 30 hello
Const declaration
// Like C this swaps out the consts with literals during compile time
const (
idKey string = "id"
nameKey = "name"
)
fmt.Println(idKey, nameKey)
id name
Arrays
Initialize array to 0 values
a := [3]int{}
fmt.Println(a)
[0 0 0]
Set to width of provided literal
b := [...]int{5 + 5, 20, 30}
fmt.Println(b)
[10 20 30]
Compare arrays
// Arrays can be compared in go, but Slices cannot
println(a == b)
false
Set by index, blank are initialized to 0
d := [6]int{1, 2: 4, 5, 5: 100}
fmt.Println(d)
[1 0 4 5 0 100]
Multidimensional array
e := [2][4]int{}
fmt.Println(e)
[[0 0 0 0] [0 0 0 0]]
Go has a simple multidimensional array implementation which incurs performance penalities
Slices
Create from an array
array := [4]int{1,2,3,4}
slice := array[:3]
fmt.Println("array:", array)
fmt.Println("slice:", slice)
array: [1 2 3 4]
slice: [1 2 3]
Shorthand initializer
a := []int{10, 0, 0}
[10 0 0]
Append two slices together
c := []int{10, 20, 30}
d := []int{1, 2: 3, 4, 5: 10}
e := append(c, d...)
fmt.Println(e)
[10 20 30 1 0 3 4 0 10]
Initialize with make
f := make([]int, 5)
fmt.Printf("%v len: %v cap %v\n", f, len(f), cap(f))
[0 0 0 0 0] len: 5 cap 5
Make with capacity
g := make([]int, 5, 32)
fmt.Printf("%v len: %v cap %v", g, len(g), cap(g))
fmt.Println()
[0 0 0 0 0] len: 5 cap 32
This sets asside 32 ints in memory to save the runtime doing extra work moving things around in memory, appending to a slice would otherwise have to create a new slice.
Copying Slices
Taking a slice of another slice or array uses a pointer, so modifying or appending items will also effect the original. See below examples on how to avoid this
Once the new slice grows beyond the max it creates a new copy, now any modifications to any value won't effect the original
a := []int{0, 1, 2}
b := a
b = append(b, 30)
b[2] = 60
fmt.Printf("a: %v len: %v cap: %v\n", a, len(a), cap(a))
fmt.Printf("b: %v len: %v cap: %v\n", b, len(b), cap(b))
a: [0 1 2] len: 3 cap: 3
b: [0 1 60 30] len: 4 cap: 6
If the new slice didn't grow beyond the original, modifications will still effect the original
c := []int{0, 1, 2}
d := c
d[2] = 60
fmt.Printf("a: %v len: %v cap: %v\n", c, len(c), cap(c))
fmt.Printf("b: %v len: %v cap: %v\n", d, len(d), cap(d))
a: [0 1 60] len: 3 cap: 3
b: [0 1 60] len: 3 cap: 3
Copy to stop slice modifications effecting original
e := []int{1, 2, 3, 4}
f := make([]int, 2)
copy(f, e[2:])
f[0] = 20
fmt.Printf("a: %v\n", e)
fmt.Printf("b: %v\n", f)
a: [1 2 3 4]
b: [20 4]
Array to slice copy
g := [5]int{1, 2, 3, 4, 5}
h := []int{99, 98, 97}
elementsCopied := copy(h[1:], g[3:])
fmt.Println("elements copied:",elementsCopied,"\ne:",g,"\nf:",h)
elements copied: 2
e: [1 2 3 4 5]
f: [99 4 5]
Looping
Go has a single loop statement: for
Standard for statement
for i:= 0; i < 5; i++ {
fmt.Println(i)
}
0
1
2
3
4
Condition only (while loop)
i := 1
for i < 100 {
fmt.Println(i)
i = i * 2
}
1
2
4
8
16
32
64
Infinite Loop
x := 0
for {
x++
fmt.Println(x)
if x == 5 {
break
}
}
1
2
3
4
5
Looping on
A for loop of a string converts UTF-8 to 32bit, so non standard characters are still treated as one
m := "moonπy"
for i, v := range m {
fmt.Printf("%v %8v %-6v\n", i, v, string(v))
}
0 109 m
1 111 o
2 111 o
3 110 n
4 127772 π
8 121 y
Countinue to label
samples := []string{"hello", "apple_Ο!"}
outer:
for _, sample := range samples {
for i, r := range sample {
fmt.Println(i, r, string(r))
if r == 'l' {
continue outer
}
}
fmt.Println("Outer")
}
0 104 h
1 101 e
2 108 l
0 97 a
1 112 p
2 112 p
3 108 l
Goto
func gotoer() {
goto done
for {
fmt.Println("Skip the infinite loop")
}
done:
fmt.Println("Done")
}
gotoer()
Done
Notes
- The value returned in a for-range loop is a copy, not a reference
Maps
Assigning to an unitialized map causes a panic
var a map[string]int
a["testKey"] = 1
panic: assignment to entry in nil map
goroutine 1 [running]:
main.main()
/tmp/main.go:7 +0x73
exit status 2
Initialize an empty map and add keys
totalWins := map[string]int{}
totalWins["lakers"] = 20
totalWins["suns"]++
fmt.Println(totalWins)
map[lakers:20 suns:1]
Initialize with map literals
x := map[int]string{
10: "wow cool",
40: "I like it",
}
fmt.Println(x)
map[10:wow cool 40:I like it]
Multi level map literal
teams := map[string][]string{
"Orcas": {"Fred", "Ralph", "Bijou"},
"Lions": {"Sarah", "Peter", "Billie"},
"Kittens": {"Waldo", "Raul", "Ze"},
}
fmt.Println(teams)
map[Kittens:[Waldo Raul Ze] Lions:[Sarah Peter Billie] Orcas:[Fred Ralph Bijou]]
Check if the key exists and print it if it does
v, ok := teams["Orcas"]
if(ok) {
fmt.Println(v)
}
[Fred Ralph Bijou]
Delete a key
delete(teams, "Orcas")
fmt.Println(teams)
map[Kittens:[Waldo Raul Ze] Lions:[Sarah Peter Billie]]
Notes
- Map keys can be any comparable type
Sets
Using a map as a set
Map keys don't duplicate
intSet := map[int]bool{}
vals := []int{1, 1, 2, 2, 3}
for _, v := range vals {
intSet[v] = true
}
fmt.Println(intSet)
map[1:true 2:true 3:true]
Trying to retrieve a key that doesn't exist gives false
fmt.Println(intSet[5])
false
Use a struct as a set
structSet := map[int]struct{}{}
var exists = struct{}{}
ints := []int{1, 1, 2, 2, 3}
for _, v := range ints {
structSet[v] = exists
}
for i, v := range structSet {
fmt.Println(i, v)
}
if _, ok := structSet[4]; ok {
fmt.Println("number is in the set")
} else {
fmt.Println("not in the set")
}
1 {}
2 {}
3 {}
not in the set
Make a set type
var exists = struct{}{}
type set struct {
m map[string]struct{}
}
func NewSet() *set {
s := &set{}
s.m = make(map[string]struct{})
return s
}
func (s *set) Add(value string) {
s.m[value] = exists
}
func (s *set) Remove(value string) {
delete(s.m, value)
}
func (s *set) Contains(value string) bool {
_, c := s.m[value]
return c
}
s := NewSet()
s.Add("Peter")
s.Add("David")
fmt.Println(s.Contains("Peter")) // True
fmt.Println(s.Contains("George")) // False
s.Remove("David")
fmt.Println(s.Contains("David")) // False
true
false
false
Switch
Standard Switch
words := []string{"a", "cow", "smile", "gopher",
"octopus", "anthropologist"}
for _, word := range words {
switch size := len(word); size {
case 1, 2, 3, 4:
fmt.Println(word, "is a short word!")
case 5:
wordLen := len(word)
fmt.Println(word, "is exactly the right length:", wordLen)
case 6, 7, 8, 9:
default:
fmt.Println(word, "is a long word!")
}
}
a is a short word!
cow is a short word!
smile is exactly the right length: 5
anthropologist is a long word!
Break out of a switch inside a loop
func looper() {
loop:
for i := 0; i < 10; i++ {
switch {
case i%2 == 0:
fmt.Println(i, "is even")
case i%3 == 0:
fmt.Println(i, "is divisible by 3 but not 2")
case i%7 == 0:
fmt.Println("exit the loop!")
break loop
default:
fmt.Println(i, "is boring")
}
}
}
looper()
0 is even
1 is boring
2 is even
3 is divisible by 3 but not 2
4 is even
5 is boring
6 is even
exit the loop!
Blank Switch
newWords := []string{"hi", "salutations", "hello"}
for _, word := range newWords {
switch wordLen := len(word); {
case wordLen < 5:
fmt.Println(word, "is a short word!")
case wordLen > 10:
fmt.Println(word, "is a long word!")
default:
fmt.Println(word, "is exactly the right length.")
}
}
hi is a short word!
salutations is a long word!
hello is exactly the right length.
switch n := rand.Intn(10); {
case n == 0:
fmt.Println("That's too low")
case n > 5:
fmt.Println("That's bigger")
default:
fmt.Println("Cool one")
}
Cool one
Strings
Converting string to runes retains emoji's as a single item
s := "Hello, π"
bs := []byte(s)
rs := []rune(s)
fmt.Println("Bytes:", bs)
fmt.Println("Runes:", rs)
Bytes: [72 101 108 108 111 44 32 240 159 140 158]
Runes: [72 101 108 108 111 44 32 127774]
Converting back to string from either still formats correctly
bytes := string(bs)
runes := string(rs)
fmt.Println("Bytes:",bytes)
fmt.Println("Runes:",runes)
Bytes: Hello, π
Runes: Hello, π
String slice
h := "Hello there"
for _, v := range(h[6:9]) {
fmt.Printf("%c", v)
}
the
Converting an int to a sting with give the UTF-8 representation
x := 50
fmt.Println(string(x))
xString := fmt.Sprintf("%v", x)
fmt.Println(xString)
2
50
Functional
Basics
Simulate optional parameters
type MyFuncOpts struct {
FirstName string
LastName string
Age int
}
func printNameAge(opts MyFuncOpts) error {
if opts.LastName != "" {
fmt.Println(opts.LastName)
}
if opts.Age != 0 {
fmt.Println("Age =", opts.Age)
} else {
fmt.Println("No age provided")
}
return nil
}
func test() {
printNameAge(MyFuncOpts {
LastName: "Clayton",
})
}
test()
Clayton
No age provided
Variadic input parameters and slices
func addTo(base int, values ...int) []int {
out := make([]int, 0, len(values))
for _, v := range values {
out = append(out, base+v)
}
return out
}
Standard call
sum := addTo(29,123,14,13)
fmt.Println(sum)
[152 43 42]
Spread an array into the parameters
a := []int{20,30,40}
sumSpread := addTo(10, a...)
fmt.Println(sumSpread)
[30 40 50]
Named return values
func divAndRemainder(numerator int, denominator int) (result int, remainder int, err error) {
if denominator == 0 {
err = errors.New("Cannot divide by zero")
return result, remainder, err
}
result, remainder = numerator/denominator, numerator%denominator
return result, remainder, err
}
x, y, err := divAndRemainder(5, 0)
if err != nil {
fmt.Println("Oh no an error:", err)
os.Exit(1)
}
fmt.Println(x, y, err)
No age provided
[152 43 42]
[30 40 50]
gobook-output-start
Oh no an error: Cannot divide by zero
exit status 1
Blank return (Go community says this is bad idea as it's unclear!)
func divAndRemainder(numerator, denominator int) (result int, remainder int, err error) {
if denominator == 0 {
return 0, 0, errors.New("Cannot divide by zero")
}
result, remainder = numerator/denominator, numerator%denominator
return
}
/tmp/main.go:45:6: divAndRemainder redeclared in this block
previous declaration at /tmp/main.go:37:86
x, y, z := divAndRemainder(10, 2)
fmt.Println(x, y, z)
5 0 <nil>
Anonymous function
for i := 0; i < 3; i++ {
x := func(j int) int {
fmt.Println("Printing", j, "from inside of an anonymous function")
return i
}(i)
fmt.Println("Printing", x, "from the value returned")
}
Printing 0 from inside of an anonymous function
Printing 0 from the value returned
Printing 1 from inside of an anonymous function
Printing 1 from the value returned
Printing 2 from inside of an anonymous function
Printing 2 from the value returned
First Class Functions
func add(i int, j int) int { return i + j }
func sub(i int, j int) int { return i - j }
func mul(i int, j int) int { return i * j }
func div(i int, j int) int { return i / j }
// Documentation for type
type opFuncType func(int, int) int
var opMap = map[string]opFuncType{
"+": add,
"-": sub,
"*": mul,
"/": div,
}
/tmp/main.go:74:6: opMap declared but not used
func run() {
expressions := [][]string{
{"2", "-", "3"},
{"2", "+", "3"},
{"2", "*", "3"},
{"2", "/", "3"},
{"2", "%", "3"},
{"two", "plus", "three"},
{"5"},
}
for _, expression := range expressions {
if len(expression) != 3 {
fmt.Println("invalid expression", expression, "(needs three parameters)")
continue
}
p1, err := strconv.Atoi(expression[0])
if err != nil {
fmt.Println(err)
continue
}
op := expression[1]
opFunc, ok := opMap[op]
if !ok {
fmt.Println("unsupported operator:", op)
continue
}
p2, err := strconv.Atoi(expression[2])
if err != nil {
fmt.Println(err)
continue
}
result := opFunc(p1, p2)
fmt.Println(result)
}
}
run()
-1
5
6
0
unsupported operator: %
strconv.Atoi: parsing "two": invalid syntax
invalid expression [5] (needs three parameters)
Shadowing
Shadow variable x
func block() {
x := 10
if x > 5 {
fmt.Println("Inner block", x)
x:= 5
fmt.Println("Reinit inner block:", x)
}
fmt.Println("Outer block", x)
}
block()
Inner block 10
Reinit inner block: 5
Outer block 10
Universe block
Redeclaring a variable in the universe block isn't detected by any linters or the shadow tool, be careful not to shadow anything in the universe block.
fmt.Println(true)
true := 10
fmt.Println(true)
true
10
Initializer in if/else statement scope
if n := rand.Intn(10); n == 0 {
fmt.Println("That's too low")
} else if n > 5 {
fmt.Println("That's too big:", n)
} else {
fmt.Println("That's a good number:", n)
}
That's a good number: 1
Notes
Scope
- Outside any function = package block
- Imports = file block
- Inside function / parameters = block
- Anything inside {} is a another block
Access
- Can access any outer scope identifiers
Call by value
Like C, Go is pass by value
type person struct{
age int
name string
}
func changeValues(i int, s string, p person) {
i = i * 2
s = "Changed string"
p.age = 600
p.name = "Changed Name"
}
Modifying the values passed in does not effect the values in the outer scope
p := person{20, "Original Name"}
i := 2
s := "Original string"
changeValues(i, s, p)
fmt.Println(i, s, p)
2 Original string {20 Original Name}
Modifying a map does effect the original, as it's a pointer
func modMap(m map[int]string){
m[0] = "Changed value"
m[1] = "This value will be deleted"
m[2] = "Added value"
delete(m, 1)
}
m := map[int]string{
0: "first",
1: "second",
}
modMap(m)
fmt.Println(m)
map[0:Changed value 2:Added value]
Modifying a slice will only effect items that have already been initialized, can't add new items.
func modSlice(s []int) {
for k, v := range s {
s[k] = v * 2
}
s = append(s, 10)
}
sl := []int{1,2,3}
modSlice(sl)
fmt.Println(sl)
[2 4 6]
Closures
Define the Person struct
type Person struct {
FirstName string
LastName string
Age int
}
- initialize a slice of 3 people
- Sort by age, then last name
- People is captured by the anonymous function closure,
people := []Person{
{"Pat", "Patterson", 16},
{"Amy", "Bobbart", 19},
{"Bob", "Bobbart", 18},
}
sort.Slice(people, func(i int, j int) bool {
return people[i].Age < people[j].Age
})
sort.Slice(people, func(i int, j int) bool {
return people[i].LastName < people[j].LastName
})
fmt.Println(people)
[{Bob Bobbart 18} {Amy Bobbart 19} {Pat Patterson 16}]
Defining a closure type
// Returns a function that multiplies by the given factor
type twoFactor func(factor int) int
func makeMult(base int) twoFactor {
return func(factor int) int {
return base *factor
}
}
twoBase := makeMult(2)
threeBase := makeMult(3)
fourBase := makeMult(4)
for i := 0; i < 10; i++ {
fmt.Println(twoBase(i), threeBase(i), fourBase(i))
}
0 0 0
2 3 4
4 6 8
6 9 12
8 12 16
10 15 20
12 18 24
14 21 28
16 24 32
18 27 36
Remind caller to do something with a closure e.g. close a file, as it's returned to caller and Go doesn't allow unused values, they know that they should do something with it
func getFile(name string) (*os.File, func(), error) {
file, err := os.Open(name)
if err != nil {
return nil, nil, err
}
return file, func() {
file.Close()
}, err
}
_, closer, err := getFile("./out.txt")
if err != nil {
log.Fatal(err)
}
defer closer()
2 3 4
4 6 8
6 9 12
8 12 16
10 15 20
12 18 24
14 21 28
16 24 32
18 27 36
gobook-output-start
2021/08/18 10:27:47 open ./out.txt: no such file or directory
exit status 1
Types
import "fmt"
Types
Terms
- Abstract Type - What a type should do, but not how it's done
- Concrete Type - Specifies what and how i.e how to store data and implement method
- Reciever - The type the method is being attached to, conventionally shorthand abbreviation of the types name
Declarations
Primitive
type Score int
Function
type Converter func(string)Score
Compound
type TeamScores map[string]Score
Struct
Methods
type Person struct {
FirstName string
LastName string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s %s, age %d", p.FirstName, p.LastName, p.Age)
}
p := Person{
"Jacky",
"C",
33,
}
p.String()
Assigning a method value and calling it
type Adder struct {
start int
}
func (a *Adder) AddTo(val int) int {
return a.start + val
}
myAdder := Adder{40}
f := myAdder.AddTo
myAdder.start = f(20)
fmt.Println(f(20))
Method Expression
f2 := Adder.AddTo
fmt.Println(f2(myAdder, 15))
// assigning untyped constants is valid
var i int = 300
var s Score = 100
var hs HighScore = 200
hs = s // compilation error!
s = i // compilation error!
s = Score(i) // ok
hs = HighScore(s) // ok
iota type (enumerations)
type MailCategory int
const (
Uncategorized MailCategory = iota
Personal
Spam
Social
Advertisements
)
fmt.Println(Spam)
2
x := Personal
if x == Advertisements{
fmt.Println("wow")
}
wow
import "fmt"
Terms
- Interface: list the methods that must be implemented by a concrete type
- Method set: the methods defined on an interface
Example of using a interface
type GoodDog interface {
roll()
pat()
}
type BadDog interface {
bark()
howl()
GoodDog
}
type Dog struct {
color string
size int
}
type Wolf struct {
Dog
}
func (Wolf) bark() {
fmt.Println("Woof!")
}
func (Wolf) howl() {
fmt.Println("Awwwwwoooooooooo!")
}
func (Dog) roll() {
fmt.Println("He rolls over")
}
func (Dog) pat() {
fmt.Println("He likes that!")
}
func main() {
hendrix := Dog{"blonde", 2}
wolfy := Wolf{Dog{"black", 10}}
var hendrix_interface GoodDog = hendrix
var wolfy_interface BadDog = wolfy
hendrix_interface.pat()
wolfy_interface.bark()
wolfy_interface.roll()
}
Interfaces are implemented as two pointers, value and type. Having a type assigned makes it non-nil
var s *string
fmt.Println(s == nil)
var i interface{}
fmt.Println(i == nil)
i = s
fmt.Println(i == nil)
Empty interface, can represent any data type
data := map[string]interface{}{}
import "fmt"
Define a struct
type person struct {
name string
age int
}
fred := person {
"Fred",
20,
}
fmt.Println(fred)
Anonymous struct
var person struct {
name string
age int
pet string
}
person.name = "bob"
person.age = 50
person.pet = "dog"
fmt.Println(person)
Shorthand
pet := struct {
name string
kind string
}{
name: "Fido",
kind: "dog",
}
fmt.Println(pet)
Notes
Initialization
- No difference between assigning empty struct literal and not assigning a value, both initialize all fields to 0
Conversions
- Structs can be type converted if fields are in the same order, with same naming and types
Comparisons
- Structs are comparable if of the same type, and no field contains a slice, map, channel or function
- Anonymous structs can be compared to a typed struct if contains same order, names and types
type firstPerson struct {
name string
age int
}
f := firstPerson{
name: "Bob",
age: 50,
}
var g struct {
name string
age int
}
g = f
fmt.Println(g == f)
Type Assertion
---
```go
import (
"log"
"fmt"
)
type conversion: change type assertion: reveal
Conversion example
type MyInt int
func main() {
var i MyInt = 20
i2 := int(i)
fmt.Println(i2)
}
20
Assertion example (happens at runtime), always check type assertions!
func main() {
var i interface{}
var mine MyInt = 20
i = mine
i2, ok := i.(MyInt)
if !ok {
log.Fatalf("unexpected type for %v", i)
}
fmt.Println(i2)
}
20
Type switch to check type and do appropriate action i is shadowing here, one of few examples where shadowing is idiomatic
func doThings(i interface{}) {
switch i := i.(type) {
case nil:
fmt.Println(i, "is of type interface{}")
case int:
fmt.Println(i, "is of type int")
case MyInt:
fmt.Println(i, "is of type MyInt")
case string:
fmt.Println(i, "is of type string")
default:
fmt.Println("I don't know what type this value is:", j)
}
}
func main() {
var j MyInt = 10
doThings(j)
}
Example in the standard library using type assertion from the standard librar
// copyBuffer is the actual implementation of Copy and CopyBuffer.
// if buf is nil, one is allocated.
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
// If the reader has a WriteTo method, use it to do the copy.
// Avoids an allocation and a copy.
if wt, ok := src.(WriterTo); ok {
return wt.WriteTo(dst)
}
// Similarly, if the writer has a ReadFrom method, use it to do the copy.
if rt, ok := dst.(ReaderFrom); ok {
return rt.ReadFrom(src)
}
// function continues...
}
Example of using fallback code when implementing a newer version of an API This comes from database/sql/driver in the standard library
func ctxDriverStmtExec(ctx context.Context, si driver.Stmt,
nvdargs []driver.NamedValue) (driver.Result, error) {
if siCtx, is := si.(driver.StmtExecContext); is {
return siCtx.ExecContext(ctx, nvdargs)
}
// fallback code is here
}
---
import "fmt"
type Employee struct {
Name string
ID string
}
func (e Employee) Descriptions() string {
return fmt.Sprintf("%s (%s)", e.Name, e.ID)
}
// Anything from Employee is promoted so Manager has direct access to it
type Manager struct {
Employee
Reports []Employee
}
func (m Manager) FindNewEmployees() []Employee {
result := []Employee{}
result = append(result, Employee{"Billy", "14560456"})
result = append(result, Employee{"Donkey", "4662456"})
return result
}
func main() {
bob := Employee{"Bob", "42248465"}
fred := Employee{"Fred", "45224865"}
bossMan := Manager{bob, []Employee{fred}}
bossMan.Reports = append(bossMan.Reports, bossMan.FindNewEmployees()...)
fmt.Println("Boss name: ", bossMan.Name)
fmt.Println("Boss id: ", bossMan.ID)
fmt.Println("Boss reports:\n")
for _, v := range bossMan.Reports {
fmt.Printf("Name: %v\nid: %v\n\n", v.Name, v.ID)
}
}
Boss name: Bob
Boss id: 42248465
Boss reports:
Name: Fred
id: 45224865
Name: Billy
id: 14560456
Name: Donkey
id: 4662456
Contexts
context.WithValue
Simple example of passing around a value through a context
func main() {
ctx := context.Background()
ctx = addValue(ctx)
readValue(ctx)
}
func addValue(ctx context.Context) context.Context {
return context.WithValue(ctx, "key", "test-value")
}
func readValue(ctx context.Context) {
val := ctx.Value("key")
fmt.Println(val)
}
Example wrapping contexts
package main
import (
"context"
"fmt"
)
func main() {
ctx := context.Background()
ctx = addValue(ctx)
fmt.Println(ctx.Value("first"))
}
func addValue(ctx context.Context) context.Context {
a := context.WithValue(ctx, "first", "first-v")
b := context.WithValue(a, "second", "second-v")
c := context.WithValue(b, "third", "third-v")
return c
}
Each Context is nested, but you can still grab the value at the bottom level
Example passing UUID using middleware
package main
import (
"context"
"log"
"net/http"
"github.com/google/uuid"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.Use(guidMiddleware)
router.HandleFunc("/ishealthy", handleIsHealthy).Methods(http.MethodGet)
http.ListenAndServe(":8080", router)
}
func handleIsHealthy(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
uuid := r.Context().Value("uuid")
log.Printf("[%v] Returning 200 - Healthy", uuid)
w.Write([]byte("Healthy"))
}
func guidMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
uuid := uuid.New()
r = r.WithContext(context.WithValue(r.Context(), "uuid", uuid))
next.ServeHTTP(w, r)
})
}
Tooling
Cmds
Upgrade version of module
go get -u=patch github.com/[user]/[repo]
List variables
go env
See where the overwrite file is for GO
go env GOENV
Test Coverage
go test -v -coverpkg=./... -coverprofile=profile.cov ./... && go tool cover -func profile.cov
Imports sorter
go install golang.org/x/tools/cmd/goimports@latest
goimports -l -w .
The -l flag tells goimports to print the files with incorrect formatting to the console. The -w flag tells goimports to modify the files in-place. The . specifies the files to be scanned: everything in the current directory and all of its subdirectories.
Go Lint
go install golang.org/x/lint/golint@latest
# run it with:
golint ./...
Go vet
Passing wrong numbers of parameters, assigning values that are never used etc
go vet ./...
golangci-lint
Many different tools run over the same directory
golangci-lint run
go generate
Run a command from comments e.g. in a file:
//go:generate mockgen -destination=mocks/mock_foo.go -package=mocks . Foo
the run
go generate
shadow detection
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
makefile to fmt, lint, vet and build
.DEFAULT_GOAL := build
fmt:
go fmt main.go
.PHONY:fmt
lint: fmt
golint main.go
.PHONY:lint
vet: fmt
go vet main.go
.PHONY:vet
build: vet
go build main.go
.PHONY:build
Use a different version of go
go get golang.org/dl/go1.15.6
go1.15.6 download
Delete new version of go
go1.15.6 env GOROOT
rm -rf $(go1.15.6 env GOROOT)
rm $(go env GOPATH)/bin/go1.15.6
.DEFAULT_GOAL := build
fmt:
go fmt main.go
.PHONY:fmt
lint: fmt
golint main.go
.PHONY:lint
vet: fmt
go vet main.go
shadow main.go
.PHONY:vet
build: vet
go build main.go
.PHONY:build
Keep your code in GOPATH which is typically ~/go/src/ Make a subdirectory for the extra package you want:
βββ go.mod
βββ go.sum
βββ main.go
βββ mypackage
βββ mypackage.go
In mypackage (make sure to capitalize anything you want to export):
package mypackage
func Double(a int) int {
return a * 2
}
Now in main.go you can import the package:
package main
import (
"fmt"
"github.com/[user]/[repo]/package_example/mypackage"
)
func main() {
num := mypackage.Double(2)
output := print.Format(num)
fmt.Println(output)
}
run:
go mod init
go mod tidy
When you upload this to github, others will be able to import mypackage via the same import line
github.com/pkg/profile
Start a cpu profile, save it to working directory
defer profile.Start(profile.CPUProfile, profile.ProfilePath(".")).Stop()
Standard Library
import(
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
client := &http.Client{
Timeout: 30 * time.Second,
}
req, err := http.NewRequestWithContext(context.Background(),
http.MethodGet, "https://jsonplaceholder.typicode.com/todos/4", nil)
if err != nil {
panic(err)
}
req.Header.Add("X-My-Client", "Learning Go")
res, err := client.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("unexpected status: got %v", res.Status))
}
fmt.Println(res.Header.Get("Content-Type"))
var data struct {
UserID int `json:"userId"`
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
}
err = json.NewDecoder(res.Body).Decode(&data)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", data)
application/json; charset=utf-8
{UserID:1 ID:4 Title:et porro tempora Completed:true}
import "fmt"
func logOutput(message string) {
fmt.Println(message)
}
type SimpleDataStore struct {
userData map[string]string
}
func (sds SimpleDataStore) UserNameForID(userID string) (string, bool) {
name, ok := sds.userData[userID]
return name, ok
}
jack
SimpleDataStore:
func NewSimpleDataStore() SimpleDataStore {
return SimpleDataStore{
userData: map[string]string{
"1": "Fred",
"2": "Mary",
import (
"compress/gzip"
"fmt"
"io"
"log"
"os"
)
Standard read file through buffer
file, err := os.Open("./out.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
data := make([]byte, 100)
for {
count, err := file.Read(data)
if err != nil {
if err == io.EOF {
return
}
log.Fatal(err)
}
if count == 0 {
return
}
fmt.Print(string(data[:count]))
}
Read through zip and use buffer for more operations
func countLetters(r io.Reader) (map[string]int, error) {
buf := make([]byte, 2048)
out := map[string]int{}
for {
n, err := r.Read(buf)
for _, b := range buf[:n] {
if (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') {
out[string(b)]++
}
}
if err == io.EOF {
return out, nil
}
if err != nil {
return nil, err
}
}
}
func buildGZipReader(fileName string) (*gzip.Reader, func(), error) {
r, err := os.Open(fileName)
if err != nil {
return nil, nil, err
}
gr, err := gzip.NewReader(r)
if err != nil {
return nil, nil, err
}
return gr, func() {
gr.Close()
r.Close()
}, nil
}
r, closer, err := buildGZipReader("./test.txt.gz")
if err != nil {
log.Fatal(err)
}
defer closer()
counts, err := countLetters(r)
if err != nil {
log.Fatal(err)
}
fmt.Println(counts
JSON
import (
"encoding/json"
"fmt"
)
f := struct {
Name string `json:"name"`
Age int `json:"age"`
}
err := json.Unmarshal([]byte(`{"name": "Bob", "age": 30}`), &f)
json pretty print
fmt.Println(json.MarshalIndent(data, "", " "))
Example
import (
"encoding/json"
"fmt"
"log"
)
type Person struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
HairColor string `json:"hair_color"`
HasDog bool `json:"has_dog"`
}
myJson := `
[
{
"first_name": "Clark",
"last_name": "Kent",
"hair_color": "black",
"has_dog": true
},
{
"first_name": "Bruce",
"last_name": "Wayne",
"hair_color": "black",
"has_dog": false
}
]`
var unmarshalled []Person
//
err := json.Unmarshal([]byte(myJson), &unmarshalled)
if err != nil {
log.Println("Error unmarshalling json", err)
}
log.Println(unmarshalled)
// write json from a struct
var mySlice []Person
var m1 Person
m1.FirstName = "Wally"
m1.LastName = "West"
m1.HairColor = "red"
m1.HasDog = false
mySlice = append(mySlice, m1)
var m2 Person
m2.FirstName = "Billy"
m2.LastName = "Bob"
m2.HairColor = "green"
m2.HasDog = false
mySlice = append(mySlice, m2)
newJson, err := json.Marshal(mySlice)
if err != nil {
log.Println("error marshalling", err)
}
fmt.Println(string(newJson))
// ## Importing external packages not working with yaegi, however it works with gomacro.
// This won't work with gobook until kernel changed to use gomacro
import (
"context"
"database/sql"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
func DoSomeInserts(ctx context.Context, db *sql.DB, value1 string, value2 string, value3 string) (err error) {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
// If the transaction fails roll back
if err != nil {
tx.Rollback()
}
err = tx.Commit()
}()
_, err = tx.ExecContext(ctx, "INSERT INTO area (building_id, name, description, created_at, updated_at) values (?, ?, ?, NULL, NULL)", value1, value2, value3)
if err != nil {
return err
}
// use tx to do more database inserts here
return nil
}
db, err := sql.Open("mysql", "root:password@/test")
if err != nil {
panic(err)
}
// See "Important settings" section.
db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)
// Use context.Background for the parent context
err = DoSomeInserts(context.Background(), db, "2", "test", "test_description")
if err != nil {
log.Fatal(err)
}
import (
"time"
"fmt"
)
Use multiply to convert units to a time.Duration
d := 2 * time.Hour + 30 * time.Minute
fmt.Println(d)
2h30m0s
Using Equal method corrects for timezones
x := time.Now()
y := x
if(x.Equal(y)){
fmt.Println("Equal!")
} else {
fmt.Println("Not equal!")
}
Equal!
Get current time with timezone
fmt.Println(time.Now())
2021-07-09 20:50:46.11267622 +0800 AWST m=+69.426266556
Very cool way of specifying date format, 1 represents seconds, 6 represents year, and 7 is timezone. Everything else is intuitive
tEarly := time.Parse("06-05-04 03:02:01", "20-01-01 01:01:01")
tLate := time.Parse("06-05-04 03:02:01", "20-01-01 12:01:01")
fmt.Println(tEarly)
2020-01-01 01:01:01 +0000 UTC
Print a portion of time
fmt.Println(tEarly.Year())
2020
Compare two times (before, after, equal)
fmt.Println(tEarly.Before(tLate))
true
Return the time elapsed between two dates
fmt.Println()
Algorithms
type IntTree struct {
val int
left, right *IntTree
}
func (it *IntTree) Insert(val int) *IntTree {
if it == nil {
return &IntTree{val: val}
}
if val < it.val {
it.left = it.left.Insert(val)
} else if val > it.val {
it.right = it.right.Insert(val)
}
return it
}
func (it *IntTree) Contains(val int) bool {
switch {
case it == nil:
fmt.Println("No Match on", val)
return false
case val < it.val:
fmt.Println("Going left", val, "<", it.val)
return it.left.Contains(val)
case val > it.val:
fmt.Println("Going right", val, ">", it.val)
return it.right.Contains(val)
default:
fmt.Println("Matched", val)
return true
}
}
func main() {
t := IntTree{}
t.Insert(10)
t.Insert(10)
t.Insert(9)
t.Insert(5)
fmt.Println(t.Contains(6))
fmt.Println(t.Contains(5))
}
import (
"errors"
"fmt"
"log"
)
Example of using an interface and type assertion to run a different method depending on the type
type treeNode struct {
val Val
lchild *treeNode
rchild *treeNode
}
type Val interface {
process(int, int) int
}
type number int
type operator byte
func (n number) process(int, int) int {
fmt.Println("Hey! Can't process this!")
return int(n)
}
func (o operator) process(left int, right int) int {
switch o {
case '+':
fmt.Printf("%v%v%v = %v\n", left, string(o), right, left+right)
return left + right
case '-':
fmt.Printf("%v%v%v = %v\n", left, string(o), right, left-right)
return left - right
case '*':
fmt.Printf("%v%v%v = %v\n", left, o, right, left*right)
return left * right
case '/':
fmt.Printf("%v%v%v = %v\n", left, o, right, left/right)
return left / right
default:
return -1
}
}
func walkTree(t *treeNode) (int, error) {
switch val := t.val.(type) {
case nil:
return 0, errors.New("invalid expression")
case number:
// we know that t.val is of type number, so return the
// int value
return int(val), nil
case operator:
// we know that t.val is of type operator, so
// find the values of the left and right children, then
// call the process() method on operator to return the
// result of processing their values.
left, err := walkTree(t.lchild)
if err != nil {
return 0, err
}
right, err := walkTree(t.rchild)
if err != nil {
return 0, err
}
return val.process(left, right), nil
default:
// if a new treeVal type is defined, but walkTree wasn't updated
// to process it, this detects it
return 0, errors.New("unknown node type")
}
}
func main() {
t := treeNode{
val: number(10),
}
t2 := treeNode{
val: number(9),
}
t3 := treeNode{
val: operator('-'),
lchild: &t,
rchild: &t2,
}
x, err := walkTree(&t3)
if err != nil {
log.Fatal(err)
}
fmt.Println(x)
}
Slow implementation
import "fmt"
type LinkedList struct {
Value interface{}
Next *LinkedList
}
func (ll *LinkedList) Insert(pos int, val interface{}) *LinkedList {
if ll == nil || pos == 0 {
return &LinkedList{
Value: val,
Next: ll,
}
}
ll.Next = ll.Next.Insert(pos-1, val)
return ll
}
l := LinkedList{
Value: 200,
}
m := LinkedList{
Value: 300,
Next: &l,
}
fmt.Println(m.Value, m.Next.Value)
300 200
{300 0xc0002ec0e0} 0xc0002ec0e0
package main
import "fmt"
func reverse(x int) int {
var result int
for x != 0 {
// multiply by 10 for placeholder for next digit
result *= 10
// Gets the last digit
xModulo := x % 10
// Add the last digit
result += xModulo
if result > 2147483647 || result < -2147483648 {
return 0
}
// strip the last digit, no remainder because int
x /= 10
}
return result
}
func main(){
x := reverse(-4232)
fmt.Println(x)
fmt.Prin
}
MongoDb
go get go.mongodb.org/mongo-driver/mongo
Golang-jwt
-
jwt.ParseWithClaims(accessToken)
-
ParseUnverified(tokenString, claims) split token on
.
, make sure contains three parts
token = &Token{Raw: tokenString}
headerBytes
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9
eyJDbGllbnRJRCI6ImM5ZjBhMmM1LTIxY2QtNDhiYS05MGJkLTA3NmUwYjIxZDQzNSIsIlRva2VuVVVJRCI6ImYzOWM2NjQwLTA1ZjEtNGU3Ny04ZTcyLWQ2ZDdmZjAyMzVjMiIsIktleUlEIjoiM2FiMTFiZDgtY2NhMC00YjMyLTlmNDctNTg2MWIwN2ZjZTc3IiwiZXhwIjoxNjM2NjI1NDM3LCJpYXQiOjE2MzY2MjQ4Mzd9
NYmcVkPDNSWSzWlATPLdxTN14dIhQiYk87jCjwC_4YsB5IiaAtezPPRVFr--7QKFHNpObjMjBRjCJcqpi9uCrQ