Lightning Intro To Go

time to read 13 min | 2602 words

The Go Programming Language

  • Go is a compiled programming language in the tradition of C and C++, with static typing, garbage collection, and unique language features enabling concurrent programming
  • Latest Release: 1.8rc3 (1.8 will be out soon)
  • Developed internally by Google to solve the kind of problems unique to Google (ie, high scale services/systems)
  • Designers/developers of Go have deep ties to C/Unix (Ken Thompson, Rob Pike, Robert Griesemer, et al)

Hello World in Go

package main
import "fmt"

func main() {
    fmt.Println("hello world")
}

Go Language and Runtime Feature Overview

  • Small and powerful standard library
  • Garbage collected
  • Statically compile (or cross-compile!) and deploy almost anywhere
  • Super-fast compiles and single binary deploys
  • Language/standard library are UTF-8 native
  • Design and behavior of language/standard library is opinionated
  • Since v1.5, compiler toolchain is written in Go
  • Built in unit testing
  • Easy integration with C code/libraries
  • Less-is-more!

Installing Go

#!/bin/bash

sudo -s
cd /usr/local/

export GOROOT_BOOTSTRAP=/usr/local/go-1.7.4

git clone https://go.googlesource.com/go
cd go/src && git checkout go1.8rc3
./all.bash

export PATH=$PATH:/usr/local/go/bin

IDEs

  • VSCode - is a new, but strong, cross-platform IDE built by Microsoft, and works well with Go!
  • JetBrains Gogland - new, upcoming JetBrains IDE for Go
  • Plugins available for most other IDEs/editors – Sublime, IntelliJ, etc

Installing VS Code with Go

Run/Build/Install command line example:

  • Run: $ go run hello.go
  • Build and Execute:

    $ go build hello.go
    $ ls
    hello hello.go
    $ ./hello
    hello world
    
  • Install (puts hello in $GOPATH/bin/): $ go install

Packages and go get

  • Go code is grouped in “packages”: a directory containing one or more .go files
  • Packages are retrievable via go get: $ go get -u github.com/knq/baseconv
  • The above will fetch the Go Git repository and store it in $GOPATH/src/$REPO:

    $ cd $GOPATH/src/github.com/knq/baseconv
    $ ls
    baseconv.go  baseconv_test.go  coverage.out  coverage.sh  example  LICENSE  old  README.md
    
  • A package may have any number of sub directories each of which is its own package, ie:

    github.com/knq/baseconv              // would import "baseconv"
    github.com/knq/baseconv/subpackage   // would import "subpackage"
    

Package Imports, and Visibility

  • Packages (ie, libraries) can be imported into the current package: import "github.com/knq/baseconv"
  • Only func’s and type’s defined in that package beginning with a capital letter are visible when imported: func doSomething() {} // not visible to other packages func DoSomething() {} // visible to other packages
  • For example:

    import (
        "fmt"
    )
    fmt.Println("foobar")
    fmt.print("hello") // compiler error
    
  • When in doubt, start the name with a capital

  • You can define an import alias for a package:

    import (
        j "github.com/knq/baseconv" 
    )
    // baseconv's exported funcs/types are now available under 'j':
    j.Decode62()
    
  • Some packages need to be imported for their side-effect:

    import (
        // imports the postgres database driver package 
        _ "github.com/lib/pq"
    )
    

Building, Testing, and Installing a Go package from command line

$ cd $GOPATH/src/github.com/knq/baseconv/
$ go build
$ go test -v
=== RUN   TestErrors
--- PASS: TestErrors (0.00s)
=== RUN   TestConvert
--- PASS: TestConvert (0.00s)
=== RUN   TestEncodeDecode
--- PASS: TestEncodeDecode (0.00s)
PASS
ok      github.com/knq/baseconv 0.002s
$ go install

Some Notes on Go’s Syntax/Design

  • Go designers have purposefully omitted many common features in other languages in the interest of simplicity and readability above almost all else
  • If it can already be done through some other feature available to the language, then there is not a need for a specific language feature

Quick Syntax Primer

  • Go is C-like, but:
  • No semicolons – every line break implies a semicolon
  • Variable names come before the type
  • Braces are required for control statements (for, if, switch, …)
  • Parentheses are not used in control statements
  • Typing is implicit in assignments
  • Unused import or variable is a compiler error
  • Trailing commas are required
  • Standard syntax formatting that is applied automatically with gofmt

C vs Go, a simple comparison

  • Example of printing all command line arguments in C and Go:
#include <stdio.h>

int main(int argc, char **argv) {
    for (int i = 0; i < argc; i++) {
        printf(">> arg %d: %s\n", i, argv[i]);
    }
    return 0;
}
package main

import (
	"fmt"
	"os"
)

func main() {
	for i, a := range os.Args {
		fmt.Printf(">> arg %d: %s\n", i, a)
	}
}

Standard Types (builtin)

var b bool = false 
var s string = "" 
var b byte = 0 // alias for uint8

// -1 0
int   uint   
int8  uint8  
int16 uint16 
int32 uint32 
int64 uint64 

// 0.0  1.0i ...
float32 complex64
float64 complex128

// 'c'
rune // alias for int32

uintptr // an integer type that is large enough to hold the bit pattern of any pointer.

Expressions and Assignments

  • Variable names can include any UTF-8 code point:

    var a = ""                  // variable "a"    is string    value ""
    var 世界 = 15                // variable "世界" is int       value 15
    var f bool                  // variable "f"    is bool      value false
    b := "awesome"              // variable "b"    is string    value "awesome"
    b = "different string"      // variable "b"    is assigned  value "different string"
    
  • Expressions:

    a := b * c // a is assigned the value of b * c
    
  • Supports multiple return values from functions:

    func someFunc() (int, int) { return 7, 10 }
    a, b := someFunc() // a = 7, b = 10
    
  • Special typeless variable _ can be used as placeholder in an assignment:

    a, b, _ = anotherFunc() // the third return value of anotherFunc will be ignored
    

Expressions and Operators

  • Usual operators:

    alt text

  • Note: operators are only are available as a single expression (cannot be inlined), ie:

    // valid
    i++
    j--
    
    // not valid
    j[i++]
    
  • Otherwise, operators work as expected:

    j *= 10
    i = i + 15
    

Constants

  • Go declares constants using the keyword “const”:

    const (
        MyString string = "foobar"
    )
    
  • A const can be any expression:

    const (
        // typed const
        MyConst string = "hello" 
    
        // not typed
        MyOtherConst = 0
    )
    
  • iota is special value for incrementing constants:

    const (
        MyConstA = iota // 0
        MyConstB        // 1
        MyConstC        // 2
    )
    

Slices, Maps, Arrays

  • There are fixed-length arrays, but rarely used:

    var a [8]byte
    
  • A slice provides a dynamic list of any type:

    var a = []int{15, 20, 9}
    for i := range a {
        fmt.Printf(">> %d\n", a[i])
    }
    
  • Maps (dictionaries/hashes in other languages) provides a robust map of key to value pairs for any type:

    var a = map[string]int{
        "foo": 10,
        "bar": 15,
    }
    for k, v := range a {
        fmt.Printf(">> %s: %d\n", k, v)
    }
    

make and new

  • make is used to allocate either a slice, map or channel with a size:

    a := make([]string, 15)              // a has type '[]string' and initial length of 15
    b := make(map[string]interface{}, 0) // b has type 'map[string]interface{}'
    c := make(chan *Point)               // c has type 'chan *Point'
    
  • new allocates a new instance of the type and returns a pointer to the allocated instance:

    b := new(Point) // b has type '*Point'
    p := &Point{}   // more or less the same as new(Point)
    i := new(int)   // i has type '*int'
    

append, len, and reslicing

  • append is used to append to a slice

    b := []string{"foo", "bar"}
    b = append(b, "another string") // b is now []string{"foo", "bar", "another string"}
    
  • len provides the length for slices, maps, or strings:

    a := map[string]int{0, 12}
    b := []int{14, 13, 3}
    len(a)       // 2
    len(b)       // 3
    len("hello") // 5
    
  • Any slice or array can be resliced:

    a := []string{"foo", "bar", "make", "new"}
    b := a[:1]  // slice a from 0 to 1      -- b is []string{"foo"}
    c := a[1:3] // slice a from 1 to 3      -- c is []string{"bar", "make"}
    d := a[1:]  // slice a from 1 to len(a) -- d is []string{"bar", "make", "new"}
    

func

  • Functions are declared with func, and the return type follows the parameter list:

    func empty() {}                                       // no return value
    func doNothing(a string, c int) error { return nil }  // returns error
    
  • A func can be assigned to a variable:

    func someFuncName() error { return nil }
    a := someFuncName // a has type 'func() error'
    
  • A func can also be declared inline:

    func main() {
        g := func() {
            doSomething()
        }
        g()
        func(b int) {
          fmt.Printf("%d\n", b)
        }(10)
    }
    

Control Statements

  • if/else if/else:

    if cond {
        expr
    } else if cond {
        expr
    } else {
        expr
    }
    
  • switch/case/default:

    switch v {
    case "e":
        // something
    default:
        // default
    }
    
  • switch does not require break statements and cases do not automatically fallthrough

Control Statements

  • switch as replacement for complex if/else chains:

    switch {
    case i == 0 && err != nil:
        // something
    case i == 6:
        // something
    case j == 9:
        // something
    default:
        // default
    }
    
  • select is like switch, but waits on a channel:

    select {
    case a := <-c:
        // read a from channel c
    case <-time.After(15*time.Second):
        // a 'timeout' after 15 seconds
    }
    

Control Statements

  • In Go, “while” is spelled “for” – the only provided loop is for:

    for cond {
    }
    
    for {
        if !cond {
            break
        }
    }
    
    loop:
        for i := 0; i < len(someSlice); i++ {
            for {
                if a == 15 {
                    break loop
                }
            }
        }
    
    for key, value := range someSlice {
    }
    

Variadic parameters

  • func can have variable args (variadic)
  • Must be last parameter
  • Special symbol … to indicate expansion:

    func doSomething(prefix string, intList ...int) {
        for m, n := range intList {
            fmt.Printf("> %s (%d): %d\n", prefix, m, n) 
        }
    }
    
  • Can be used also in append statements:

    strList := []string{"bar"}
    j := append([]string{"foo"}, strList...) // j is []string{"foo", "bar"}
    

Type Declaration

  • No classes or objects
  • struct provides compound (“structured”) types:

    type Point struct {
        X, Y float64
    }
    
  • and interface defines a set of func’s:

    type Reader interface {
        Read([]byte) (int, error)
    }
    
  • Can create a type copy for any other type:

    type MyUnsignedInteger uint32
    type MyPoint Point
    type MyReader Reader
    type MyFunc func(string) error
    

Notes on Types

  • Same export / visibility rules apply for struct members:

    type Point struct {
        X, Y float64
        j    int
    }
    
  • Only Go code in the same package as Point can see Point.j

  • Type conversions (casts) are always explicit!

    type MyStr string
    a := MyStr("blah")       // a is of type MyStr and has value "blah"
    var b string = a         // compiler error
    var c string = string(a) // c is of type string and has value "blah"
    

Type Receivers

  • A func can be given a receiver:

    type MyType struct {
        MyValue int
    }
    func (mt MyType) AddOne() int {
        return mt.MyValue+1
    }
    
    type MyString string
    func (ms MyString) String() string {
        return string(ms)
    }
    
  • A func with a pointer receiver, allows the func to modify the value of the receiver:

    // Increment increments MyType's MyValue and returns the result.
    func (mt *MyType) Increment() int {
        mt.MyValue++
        return mt.MyValue
    }
    

About interface

  • Unlike Java or other object-oriented languages, there is no need to explicitly declare a type as having an interface:

    type Reader interface {
        Read([]byte) (int, error)
    }
    
    type MyReader string
    
    // Read satisfies the Reader interface. 
    func (mr MyReader) Read([]byte) (int, error) { /* ... */ }
    
    // DoSomething does something with a Reader.
    func DoSomething(r Reader) { /* ... */ }
    
    func main() {
        s := MyReader("hello")
        DoSomething(s)
    }
    

Pointers, Interfaces, and nil

  • Go pointers are similar to pointers in C/C++ (address to a variable), but there is no pointer math in Go!
  • The . operator is used for both pointer dereference and for accessing a member variable:

    type Point { X, Y int }
    
    a := Point{10, 20}        // a has type 'Point' with value {X: 10, Y: 20}
    b := &Point{X: 30, Y: 40} // b has type '*Point' and **points** to value {X: 30, Y: 40}
    *b = Point{Y: 80}         // b now points to value {X: 0, Y: 80}
        
    // . is used to access struct members for both a and b:
    fmt.Printf("(%d %d) (%d %d)", a.X, a.Y, b.X, b.Y) // prints "(10 20) (0 80)"
    
  • Any type pointer or interface can be assigned the nil value:

    type Reader interface{}
    var a *Point = nil
    var b MyReader = nil
    

Goroutines and Channels

  • Killer features of Go that provides lightweight concurrency in any Go application
  • Any func in Go can be a goroutine:

    func main() {
        for i := 0; i < 10; i++ {
            go func(z int) {
                fmt.Printf(">> %d\n", z)
            }(i)
        }
        time.Sleep(1*time.Second)
    }
    
  • Channels are a unique feature in Go that provides type safe memory sharing

    c := make(chan int)
    c <- 10  // write 10 to c
    j := <-c // read int from c
    
    // channels can be read or write only:
    var c <-chan int // read only chan
    var d chan<- int // write only chan
    

Goroutine and Channel example

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup

    c := make(chan int)
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(z int) {
            defer wg.Done()
            time.Sleep(1 * time.Second)
            c <- z
        }(i)
    }

    wg.Add(1)
    go func() {
        defer wg.Done()
        for {
            select {
            case z := <-c:
                fmt.Printf(">> z: %d\n", z)
            case <-time.After(5 * time.Second):
                return
            }
        }
    }()

    wg.Wait()
    fmt.Println("done")
}

Handling Errors

  • No try/case equivalent
  • Breaking flow should be done by checking for an error:

    func MyFunc() error {
        return errors.New("error encountered")
    }
    
    err := MyFunc()
    if err != nil {
        // handle error
    }
    
  • Utility func in the standard library fmt package:

    fmt.Errorf("encountered: %d at %s", line, str) // returns a error
    

Error Types

  • error is a special Go interface:

    interface error {
        Error() string
    }
    
  • Any type can be an error by satisfying the error interface:

    type MyError struct {}
    func (me *MyError) Error() string {
        return "my error"
    }
    
    func doSomething() error {
        return &MyError{}
    }
    

defer

  • defer is a great feature of Go that allows executes the func when the parent func returns:

    func doSomething() error {
        db, err := sql.Open(/* ... */)
        if err != nil {
            return err
        }
        defer db.Close()
    
        err = db.Exec("DELETE ...")
        /* ... */
    }
    

    panic and recover

  • panic allows immediate halt of the current goroutine:

    panic("some error")
    
  • recover() can only be called in defer’d func’s, but allows recovery after a panic:

    func myFunc() {
        defer func() {
            if e := recover(); e != nil {
                log.Printf("run time panic: %v", e)
            }
        }()
    
        panic("my panic")
    }
    
  • Note: panic’s should not be used unless absolutely necessary.

Quick Overview of the Standard Library

```golang
import (
    "fmt"      // string formatting
    "strings"  // string manipulation
    "strconv"  // string conversion to standard types
    "io"       // system input/output package
    "sync"     // synchronization primitives
    "time"     // robust time handling/formatting
    "net/http" // http package supporting http servers and clients

    "database/sql"  // standardized sql interface
    "encoding/json" // json encoding/decoding (also packages for xml, csv, etc)

    // template libraries for text and html
    "text/template"
    "html/template" 

    // cryptographic libs
    "crypto/rsa"
    "crypto/elliptic"

    "reflect"       // go runtime introspection / reflection
    "regexp"        // regular expressions
)
// And many many many more!
```

What Go doesn’t have (and why this is a good thing)

  • Generics
  • Implicit comparisons
  • Overloading / Inheritance
  • Objects
  • Ternary operator (?:)
  • Miscellany data structures (vector, set, etc)
  • Package manager

Working Example

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func main() {
	// read from web
	res, err := http.Get("http://ifconfig.co/json")
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v", err)
		os.Exit(1)
	}
	defer res.Body.Close()

	// read the body
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v", err)
		os.Exit(1)
	}

	// decode json
	var vals map[string]interface{}
	err = json.Unmarshal(body, &vals)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v", err)
		os.Exit(1)
	}

	for k, v := range vals {
		fmt.Fprintf(os.Stdout, "%s: %v\n", k, v)
	}
}