Introduction to Go
The not so short version
Christian Joergensen
Co-founder, CTO at Ubivox Technologies
Christian Joergensen
Co-founder, CTO at Ubivox Technologies
fmt, doc, vet, test/usr/localwget https://.../go1.9.2.linux-amd64.tar.gz tar -C /usr/local -xzf go1.9.2.linux-amd64.tar.gz
Contains:
Go finds your code using these environment variables:
GOPATH where your local go code lives: ~/goGOROOT where your downloaded go distribution lives: /usr/local/go
Also, add these to your path to PATH:
$GOPATH/bin$GOROOT/binNow we're ready:
$ go version go version go1.9.2 linux/amd64
A few gotchas:
Coming from C, a declaration of two integers would read:
int a, b;
In Go, the order is reversed:
var a, b int
Short declarations using type inference with:
var s = "Hello world"
Or even shorter:
s := "Hello world"
if a == 42 {
print("hello")
}
for i := 0; i < 10; i++ { ... }
var c *http.Client = &http.Client{}
c := &http.Client{}for loopsThe naked loop (do)
for { ... }The single condition loop (while)
for cond { ... }The Regular loop (for)
for init; cond; post { ... }Turns:
err := process()
if err != nil {
logError(err)
return err
}Into:
if err := process(); err != nil {
logError(err)
return err
}nil valueMostly what you would expect, though:
var s string // "" var i int // 0 var b bool // false
What one would expect:
boolint, int8, int16, int32, int64uint, uint8, uint16, uint32, uint64float32, float64complex64, complex128byte (uint8), rune (int32)stringSlices, arrays:
var a []int var b [][]int var c [8]int
Maps:
var m map[string]int var n map[string]map[string][]int
Must be initialized:
a := make([]int) b := make([]int, length, capacity) c := new([8]int) m := make(map[string]int) m := make(map[string]int, capacity)
package main
import "fmt"
func main() {
m := make(map[string]string) m["a"] = "Hello" fmt.Printf("a: %s, b: %s\n", m["a"], m["b"]) if value, found := m["b"]; !found { fmt.Println("'b' not found in map") } else { fmt.Printf("b: %s", value) } if _, found := m["b"]; !found { fmt.Println("'b' really not found in map") }
}
package main
import "fmt"
func main() {
s := "Hello World" fmt.Printf("Hello %s\n", s[6:]) fmt.Printf("%s World\n", s[:5]) l := []int{5, 10, 15, 20} fmt.Printf("Slice: %v\n", l[1:3])
}
type Amount float64
func (a Amount) VAT() Amount {
return a * 0.25
}package main
import "fmt"
func main() {
var a *int b := 42 a = &b b = 60 fmt.Printf("a: %d, b: %d\n", *a, b)
}
Class'ish types composited of other types:
type Employee struct {
Name string
Department string
Salary float64
}To create a new instance, use either a composite literal:
e := Employee{
Name: "Christian",
Department: "Development",
Salary: 1000,
}
Or allocate a zero instance and get a pointer with new. Then access the fields:
e := new(Employee) e.Name = ...
Pointer or non-pointer struct?
If a struct requires initialization, create a constructor function.
The naming convention is:
func NewEmployee(name, department string, salary float64) *Employee {
...
}If it makes sense, strive to make the struct useful in a "zero" state. Examples:
22Typical function declaration:
func TwoInts(a int) (int, int) {
return 60 + a, 80 - a
}
var otherName func(int) (int, int)
a, b := TwoInts(10)Unused return values must be ignored:
_, b := TwoInts(10)
Varadic parameters:
func Sum(numbers ...int) int {
// type of numbers will be []int
}
sum := Sum(42, 60)Expanded:
numbers := []int{42, 60}
sum := Sum(numbers...)receiver, and must be of the associated typefunc (e *Employee) GiveRaise(percentage float64) {
e.Salary *= 1 + percentage/100
}
func (e Employee) Greet() {
fmt.Printf("Hello %s\n", e.Name)
}type Greeter interface {
Greet()
}
var g Greeter
g = Employee{...}
g.Greet()
g.GiveRaise(13) // Oops: g.GiveRaise undefined (type Greeter has no field or method GiveRaise)
e := g.(Employee)
e.GiveRaise(13)mainmain.mainimport keyword.package main import "fmt" func main() { fmt.Println("Hello world!") }
Example: I have a package for implementing SMTP servers living on Github:
$ go get github.com/chrj/smtpd
Checks out the master branch in: $GOPATH/github.com/chrj/smtpd
GOMAXPROCS)main function returns
To start a new goroutine that executes a function concurrently, use the go keyword followed by a function call:
go expensiveCalculation()
make function before use:var c chan int c = make(chan int) // Unbuffered c = make(chan int, 10) // Buffered
Send operator:
c <- 5
Receive operator:
a := <-c
Beware of edge cases: zero (nil) or closed channel send/receive
31package main
import (
"errors"
"fmt"
"math/rand"
"time"
)
func main() {
valuec := make(chan int) errorc := make(chan error) go worker(valuec, errorc) for { select { case v := <-valuec: fmt.Printf("got value: %d\n", v) case err := <-errorc: fmt.Printf("got error: %v\n", err) } }
}
func worker(valuec chan int, errorc chan error) {
for {
select {
case valuec <- rand.Intn(10):
case errorc <- errors.New("error"):
}
time.Sleep(time.Second)
}
}
Use range loops for iteration:
index, [,rune])index, [,item])index, [,item])key [,value])item)for i := range s { ... } // String
for i, r := range s { ... } // String
for key := range m { ... } // Map
for key, value := range m { ... } // Map
for i := range s { ... } // Slice, Array
for i, item := range s { ... } // Slice, Array
for msg := range c { ... } // ChannelA small example using both a goroutine and a channel for communication, read from a range loop:
package main
import (
"fmt"
"time"
)
func main() { c := make(chan string) go pinger(c) for msg := range c { fmt.Println(msg) } } func pinger(c chan string) { for { time.Sleep(time.Second) c <- fmt.Sprintf("Ping: %s", time.Now().Format(time.RFC3339)) } }
Consider this:
var m sync.Mutex
func Update(...) {
m.Lock()
defer m.Unlock()
// Critical area
}var Author = "Christian" var secret = "hunter2"
CamelCase for all namesvar e Employee var rd io.Reader var rw http.ResponseWriter
'...er'):type Reader interface {
Read(p []byte) (n int, err error)
}For runtime errors, such as:
Aborts execution of the current goroutine. Can be recovered using recover() in a defer statement, for exception like semantics inside a function:
defer func() {
if p := recover(); p != nil {
log.Printf("run time panic: %v", p)
}
}()Error handling in Go feels very old fashioned at first. But it will grow on you.
The error type is a builtin interface:
type error interface {
Error() string
}Most often used in function declarations as the last parameter:
func Read(p []byte) (n int, err error)
Common construct is wrapping a function in an if statement and on a failed call
return the error annotated with some extra details:
if err := ReadFromNetwork(buf); err != nil {
return errors.Wrap(err, "read from network failed")
}
handle(buf)errors package here is github.com/pkg/errors
The net package defines its own Error type:
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}We can work with that:
for {
if err := ReadFromNetwork(buf); err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(5 * time.Second)
continue
}
return err
}
break
}
handle(buf)func ParseConfigFile(filename string) (*Config, error) { ... }
config, _ := ParseConfigFile("/etc/program.yaml)A better solution: Turn errors into panics
config := MustParseConfigFile("/etc/program.yaml)Must*The standard library has a couple of clever interfaces in the io package that should be embraced everywhere it makes sense.
These interface are abstractions for everything that deals with byte-based I/O:
package io
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}These are supported everywhere: File IO, Network IO, HTTP, RPC as well as for encoding/decoding various formats: JSON, XML, CSV, images.
43Finally, I'm going to go through a couple of real world applications of Go that most modern programmers will use at some point. In particular:
Go has a production ready HTTP server in the standard library:
package http
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func ListenAndServe(addr string, handler Handler) error
All comminication with the client goes through the ResponseWriter interface:
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
You may recall the io.Writer interface. This is satisfied by the ResponseWriter type. So everything that can write, can write to an HTTP response.
Having only a single handler for a whole server is not very useful. So in order to do path or host based routing, we need a request multiplexer. The http package has a simple one:
package http type ServeMux func NewServeMux() *ServeMux func (mux *ServeMux) Handle(pattern string, handler Handler) func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
A default instance is available with the Handle and HandleFunc methods exposed at package level.
package http func Handle(pattern string, handler Handler) func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
The bundled ServeMux is very basic. I prefer working with the Gorilla toolkit, which also has support for:
There is a ton of other multiplexers available:
http.Handler interface.A complete example:
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/hello", func(rw http.ResponseWriter, req *http.Request) { fmt.Fprintf(rw, "Hello World!\n") }) log.Fatal(http.ListenAndServe(":8123", nil)) }
Despite the dynamic nature of JSON, Go has excellent JSON support.
io.Reader, io.Writer)Annotate your structs with tags:
type Employee struct {
Name string `json:"name"`
Department string `json:"department"`
Salary float64 `json:"salary"`
}
Encode using the json.Marshal function:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Employee struct { Name string `json:"name"` Department string `json:"department"` Salary float64 `json:"salary"` } func main() { e := Employee{ Name: "Christian", Department: "Development", Salary: 1000, } if encoded, err := json.Marshal(e); err != nil { log.Fatal(err) } else { fmt.Printf("%s\n", encoded) } }
Decode using the json.Marshal function:
package main
import (
"encoding/json"
"fmt"
"log"
)
type Employee struct { Name string `json:"name"` Department string `json:"department"` Salary float64 `json:"salary"` } func main() { data := []byte(`{"name":"Christian","department":"Development","salary":1000}`) e := Employee{} if err := json.Unmarshal(data, &e); err != nil { log.Fatalf("error while decoding data: %v", err) } else { fmt.Printf("Decoded: %s (salary: %.2f)\n", e.Name, e.Salary) } }
If you're encoding or decoding to something that supports the io.Reader or io.Writer interfaces use:
func (e *Employee) Write(wr io.Writer) error {
return json.NewEncoder(wr).Encode(e)
}
func Load(rd io.Reader) (*Employee, error) {
e := Employee{}
return &e, json.NewDecoder(rd).Decode(&e)
}Works very well with HTTP requests / responses.
53In this example I'm going to write a web service exposing a database of employees in JSON:
type Employee struct { Name string `json:"name"` Department string `json:"department"` Salary float64 `json:"salary"` } type EmployeeRegistry map[int]Employee
I will attach a ServeHTTP method to allow the map to implement the http.Handler interface:
func (er EmployeeRegistry) ServeHTTP(rw http.ResponseWriter, req *http.Request) { ... }Here I configure the routes, and start the HTTP server
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
// BEGIN types OMIT
type Employee struct {
Name string `json:"name"`
Department string `json:"department"`
Salary float64 `json:"salary"`
}
type EmployeeRegistry map[int]Employee
// END types OMIT
// BEGIN handler OMIT
func (er EmployeeRegistry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
i, err := strconv.Atoi(mux.Vars(req)["id"])
if err != nil {
http.Error(rw, "Couldn't decode ID", http.StatusBadRequest)
return
}
if e, found := er[i]; !found {
http.NotFound(rw, req)
} else {
rw.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(rw).Encode(e); err != nil {
log.Printf("error encoding reply: %v", err)
}
}
}
// END handler OMIT
func main() { er := EmployeeRegistry{} er[60] = Employee{Name: "Christian", Department: "Development", Salary: 1000} er[80] = Employee{Name: "John", Department: "Management", Salary: 500} r := mux.NewRouter() r.Handle("/employee/{id:[0-9]+}", er) log.Fatal(http.ListenAndServe(":8123", r)) }
The Handler:
func (er EmployeeRegistry) ServeHTTP(rw http.ResponseWriter, req *http.Request) { i, err := strconv.Atoi(mux.Vars(req)["id"]) if err != nil { http.Error(rw, "Couldn't decode ID", http.StatusBadRequest) return } if e, found := er[i]; !found { http.NotFound(rw, req) } else { rw.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(rw).Encode(e); err != nil { log.Printf("error encoding reply: %v", err) } } }
Differences between:
go buildgo installgo getBuild a single file:
go build -o employee-server main.go
Build and install a program:
go install github.com/chrj/employee-server
Fetch, build and install a program:
go get github.com/chrj/employee-server
GOOS and GOARCHandroid darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris windows386 amd64 amd64p32 arm arm64 ppc64 ppc64le mips mipsle mips64 mips64le s390xExamples:
GOOS=windows GOARCH=amd64 go install github.com/chrj/employee-server GOOS=darwin GOARCH=amd64 go install github.com/chrj/employee-server GOOS=linux GOARCH=amd64 go install github.com/chrj/employee-server
Turns:
b = MyType{
Prop: "Value",
OtherProp: 60,
}Into:
b = MyType{
Prop: "Value",
OtherProp: 60,
}$ go doc net/http.Server.Serve
func (srv *Server) Close() error
Close immediately closes all active net.Listeners and any connections in
state StateNew, StateActive, or StateIdle. For a graceful shutdown, use
Shutdown.
Close does not attempt to close (and does not even know about) any hijacked
connections, such as WebSockets.
Close returns any error returned from closing the Server's underlying
Listener(s).Webserver:
$ godoc -http=:8081
Example checks:
There is a lot of other tools for static analysis. I prefer to run them using:
Runs a lot of different linters concurrently.
62*_test.go.Let's test this:
func Sum(numbers ...int) int { result := 0 for _, number := range numbers { result += number } return result }
With this test case:
func TestSum(t *testing.T) { cases := []struct { Numbers []int Expected int }{ {[]int{2, 15, 25}, 42}, {[]int{-5, 5, 20, 28, 32}, 80}, {[]int{-2, -8, -10, -18, -22}, -60}, } for _, c := range cases { if s := Sum(c.Numbers...); s != c.Expected { t.Errorf("got: '%d', expected: '%d' (input: %v)", s, c.Expected, c.Numbers) } } }
$ go test . ok github.com/chrj/go-talks/2017/intro/sum 0.001s
Also, don't be afraid to dive into the standard library source code and learn:
github.com/golang/go/tree/master/src
65Christian Joergensen
Co-founder, CTO at Ubivox Technologies