Go
Why Go?
- clean package management system
- cross compilation
- rich standard library
- concurrency
Why not Go?
- binary size
- verbosity
Build flags
- buildvcs=false omits version control information
- ldflags=
- "-w -s" strips binaries
- "-X path/to/a/variable" change variable value at compile time
- CGO_ENABLED
- =0 ideal for scratch docker image (no host OS to be bundled). It uses a basic Go implementation
- =1 uses the native OS libraries. It implies a smaller binary size but relies on delivering a host OS too. It must be set to 1 if your application imports a package with C code.
for idx, value := range &a{} // a is copied
for idx, value := range a{} // a is not copied
uintptr // type containing an integer of a memory address and not a reference to a pointer
Go type-safe pointer → keeps its reference to the runtime without Garbage Collector problem
unsafe.Pointer → can be garbage collected
Variables are passed by value
In Go, arrays are passed by value.
var t1 [5]int32
var t2 [5]int32{0,0,0,0,0}
var t3 [...]int32{0,0,0} // number of elements in the array are computed at compile time
Slices are passed by reference.
s1 := t1[:]
s2 := []int{0,1,2,3}
Except for slices and maps
TODO: examples For slices: values can be changed but you cannot add elements. For maps: any changes are reflected in the variable passed into the function.
Pretty print - table print
package main
import (
"fmt"
"os"
"strings"
"text/tabwriter"
)
func main() {
writer := tabwriter.NewWriter(os.Stdout, 0, 4, 0, '\t', 0)
table := [][]string{{"A", "B"}, {"A", "B"}, {"A", "B"}}
for _, line := range table {
fmt.Fprintln(writer, strings.Join(line, "\t")+"\t")
}
writer.Flush()
}
# Output
A B
A B
A B
Download indication
package main
import (
"fmt"
"strconv"
"strings"
"time"
)
func lenItoa(i int) int {
return len(strconv.Itoa(i))
}
func main() {
var s string = "Downloading: 0%"
fmt.Print(s)
for i := 0; i <= 100; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Print(strings.Repeat("\b", lenItoa(i)+1))
fmt.Printf("%d%%", i)
}
fmt.Println()
}
Memoization in Go
Closures in Go can help you perform memoization on a specific function prototype.
Closures are functions declared inside of functions. The inner function is able to access and modify variables declared in the outer function.
package main
import (
"fmt"
"time"
)
func add(a, b int) int {
time.Sleep(5 * time.Second)
return a+b
}
func memoization(f func(int,int)int) func(int,int)int{
cache := make(map[string]int)
return func(a,b int) int{
i := fmt.Sprintf("%d+%d",a,b)
if v,ok := cache[i]; ok {
return v
}
res := f(a,b)
cache[i] = res
return res
}
}
func main() {
println("add(1,1) without memoization")
start := time.Now()
// the execution will last at least 5 seconds
println("res = ", add(1,1), time.Since(start).String())
addMemoized := memoization(add)
println("add(1,1) with memoization 1/n")
start = time.Now()
// the execution will last at least 5 seconds
println("res = ", addMemoized(1,1), time.Since(start).String())
println("add(1,1) with memoization 2/n")
start = time.Now()
// the execution will be instant
println("res = ", addMemoized(1,1), time.Since(start).String())
}
Gracefully shutdown your server
var err error
ctx := context.Background()
done := make(chan os.Signal, 1)
errChan := make(chan error, 1)
signal.Notify(done, os.Interrupt, syscall.SIGTERM)
serv := &http.Server{
Addr: addr,
Handler: routes,
}
go func() {
err = srv.ListenAndServe()
if err != nil {
errChan <- err
}
}()
select {
case <- done:
case <- errChan:
// do something with the error
}
err = srv.Shutdown()
// do something with the error
log.Println("server stopped")
Hot reload
SIGHUP
is a signal sent to a process when its controlling terminal is closed. It is common practice for daemonized process to use SIGHUP
for configuration reload .This allows us to not terminate the process while making changes.
kill -SIGHUP PID
package main
import (
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
)
type config struct {
Msg string
}
var conf = &config{Msg: "Hello World"}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write(([]byte(conf.Msg)))
})
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGHUP)
go http.ListenAndServe(":8000", nil)
fmt.Println("Program PID:", os.Getpid())
for {
// blocking if no default
select {
case <-sigs:
conf.Msg = "Go Go Go"
}
}
}
$ ./server &
Program PID: 17247
$ curl localhost:8000
Hello World
$ kill -SIGHUP 17247
$ curl localhost:8000
Go Go Go
Using a map as a Set
There are 2 possibilities. The first one is using bool as value: it uses one byte in memory. But it is easier to read/understand.
intSet := map[int]bool{}
if intSet[100] {
print("100 is in the Set")
}
The second one is using empty struct as value: it uses 0 byte in memory but is a bit trickier.
intSet := map[int]struct{}{}
if _, ok := intSet[100]; ok {
print("100 is in the Set")
}
Benchmark in Go
Functions start with 'Benchmark'.
# default, runs using all CPUs available. Otherwise, -cpu=X
go test -bench=.
# -count X -> runs X times each benchmark function
# -benchmem -> include memory allocation statistics
Output format
BenchmarkFunctionName-<number-of-CPU> <number-of-execution> <speed-of-each-operation>
# number-of-execution = b.N
The internal package in Go
.
├── bar
│ └── bar.go # Cannot access
├── foo
│ ├── foo.go # Can access
│ ├── internal
│ │ └── internal.go # Exported identifiers are only accessible to the direct package of 'internal' and the siblings package of 'internal'
│ └── sibling
│ └── sibling.go # Can access
└── go.mod
Type Alias
In the following example, Bar
is an alias of Foo
. It has everything Foo
has and it does not need type conversion.
type Foo struct{}
func (f Foo) Hello() string {
return "Hello"
}
type Bar = Foo