Skip to main content
czerasz.com: notes
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

Go

Useful Commands

  • print n lines of source code around the bug:

    go vet -c n
    

Writing good Golang Packages

Golang package structure:

  • cmd - commands
  • pkg - package code
  • testdata

Tips:

  • Put test code in different package.

    This way you will experience how users use the code.

    package proj_test
    
    func Test(t *testing.T) {
    	g := proj.FormatGreeter("Hi %s")
    	g.Greet("Piotr")
    }
    
  • use godoc early:

    godoc -http=:8080
    

7 Golang Mistakes

  • types can express state & behaviour.
  • interfaces are behavioural only.
  • not using io.Reader, io.Writer
  • Methods vs Functions

Smaller Builds

go build -ldflags="-s -w" main.go

Resource: How to Shrink a Go Executable by 70% in 30 Seconds

Tests

  • Run specific tests:

    go test -run=TestAdd
    

    or

    go test -run=TestAdd/sub-test-1
    
  • use environment variables to enable integration tests:

    func TestIntegration(t *testing.T) {
    	fooAddr := os.Getenv("FOO_ADDR")
    	if fooAddr == "" {
    		t.Skip("set FOO_ADDR to run this test")
    	}
    
    	f, err := foo.Connect(fooAddr)
    	// ...
    }
    

    Read more here.

Unzip/Readers

Read line by line:

gzReader, err := gzip.NewReader(out.Body)
if err != nil {
  log.Fatalf("error while creating gzip reader: %s", err)
}

count := 0
scanner := bufio.NewScanner(gzReader)
for scanner.Scan() {
  fmt.Println(len(scanner.Bytes()))
  count = count + 1
}

if err := scanner.Err(); err != nil {
  log.Fatal(err)
}

log.Fatalf("read lines: %d", count)

Pipe all to stdout:

gzReader, err := gzip.NewReader(out.Body)
if err != nil {
  log.Fatalf("error while creating gzip reader: %s", err)
}

if m, err := io.Copy(os.Stdout, gzReader); err != nil {
	log.Printf("size: %d", m)
	log.Fatalf("error while reading gzip reader: %s", err)
}

Read chunks:

gzReader, err := gzip.NewReader(out.Body)
if err != nil {
  log.Fatalf("error while creating gzip reader: %s", err)
}

gzb := make([]byte, 1024)
for {
	m, err := gzReader.Read(gzb)
	if err != nil && err != io.EOF {
		log.Fatalf("error while reading gzip: %s", err)
	}
	if m == 0 {
		break
	}
	log.Printf("content: %s", string(gzb))
	log.Printf("size: %d", m)
}

Go Installation

wget https://storage.googleapis.com/golang/go1.9.1.linux-amd64.tar.gz
sha256sum go1.9.1.linux-amd64.tar.gz | grep 07d81c6b6b4c2dcf1b5ef7c27aaebd3691cdb40548500941f92b221147c5d9c7
tar -xzvf go1.9.1.linux-amd64.tar.gz
export GOROOT=$PWD/go
export PATH=$PATH:$GOROOT/bin

or install globally:

mv ./go /usr/local/go

and add to ~/.zshrc:

# Include Go into PATH
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin/

Go Commands

Show all environment variables used by go:

go env

Show specific environment variable used by go:

go env GOOS

Functions

  • arguments with the same type:
func add(a int, b int) int {
  return a + b
}

or

func add(a, b int) int {
  return a + b
}
  • return multiple values
func switch(a, b string) (string, string) {
  return b, a
}

a, b := switch("hi", "there")
  • “naked” return

Variables

  • define multiple variables
var a, b, c int
  • variable with initializer
var a int = 1
var a = 1
var a, b int = 1, 2
  • defining blocks
var (
  ToBe   bool       = false
  MaxInt uint64     = 1<<64 -
)
  • short variable declaration - available only in functions:
k := 3
  • basic types

    • bool
    • string
    • int, int8, int16, int32, int64
    • uint uint8, uint16, uint32, uint64, uintptr
    • byte - alias for uint8
    • rune - alias for int32, represents a Unicode code point
    • float32, float64
    • complex64, complex128
  • alias

type Age int

var a Age = 5
var b int = a
  • type conversions
var i int = 42
var f float64 = float64(i)
  • constant
const Pi = 3.14
  • view variable type
fmt.Printf("v is of type %T\n", v)
  • check interface type
things := []interface{}{"hi", 5, 3.8, "there", nil, "!"}

for _, t := range things {
  tt := reflect.TypeOf(t)
  if tt != nil {
    fmt.Println("type: ", tt.Kind())
  } else {
    fmt.Println("type: nil")
  }
}
  • array vs slice

Slice is an dynamic array. Create slice with:

var s2 []int

or with (initialized with the zero values):

s2 := make([]int, 5)

Slices are arrays. If you append an item to an slice it changes it’s pointer to the resulting array.

create slice with length and capacity:

b := make([]int, 0, 5)
  • append to slice
s = append(s, 1)
s = append(s, 2, 3, 4)
  • copy slice
// Create slice with the same length
c := make([]string, len(s))
copy(c, s)
  • create multidimensional slice
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
  innerLen := i + 1
  twoD[i] = make([]int, innerLen)
  for j := 0; j < innerLen; j++ {
    twoD[i][j] = i + j
  }
}
  • iterate slice
for i := 0; i < len(s1); i++ {
  fmt.Println(i, s1[i])
}

Using range:

for i, x := range(s1) {
  fmt.Println(i, x)
}
  • for/while

Forever:

for {
  ...
}

While:

i := 0
for i < 10 {
  i = i + 1
}
  • Maps are key-value pairs
m := map[int]string{1: "one", 2: "two", 3:"three"}

Create empty map:

m = make(map[string]Vertex)

Check if map key exists

v, ok := m[4]

Remove map key:

delete(m, key)
  • iterate map
for k, v := range m {
  fmt.Println(k, ":", v)
}
  • if with a short statement
if v := 1; v < 10 {
  fmt.Println("if is truthy")
}
  • switch

Syntax:

switch i {
case 0:
  ...
case f():
  ...
default:
  ...
}

Example

switch os := runtime.GOOS; os {
case "darwin":
  fmt.Println("MacOS")
default:
  fmt.Println("other os:", os)
}

switch with no condition:

switch {
case t.Hour() < 12:
	fmt.Println("Good morning!")
case t.Hour() < 17:
	fmt.Println("Good afternoon.")
default:
	fmt.Println("Good evening.")
}
  • interface type switch
func do(i interface{}) {
	switch v := i.(type) {
	case int:
		fmt.Printf("is int")
	case string:
		fmt.Printf("is string")
	default:
		fmt.Printf("I don't know about type %T!\n", v)
	}
}

do(123)
  • create multidimensional slice
var (
  image [][]uint8
  x int
  y int
)
image = make([][]uint8, dy)
y = dy - 1

for y >= 0 {
  x = dx - 1
  image[y] = make([]uint8, dx)

  for x >= 0 {
    image[y][x] = uint8(x * y)
    x = x - 1
  }
  y = y - 1
}
  • function values
hypot := func(x, y float64) float64 {
	return math.Sqrt(x*x + y*y)
}
  • defer

Defer is working as a stack: FILO (First-in Last-out). So the first defer instruction will be executed the last.

  • rescue
func rescue() {
	if recover() != nil {
		fmt.Println("Panic has been recover")
	}
}

func main() {
	// At the end of main, call rescue function
	defer rescue()

	fmt.Println("printed")
	panic(1)
  fmt.Println("never reached")
}
  • readers

read everything from a Reader:

ioutil.ReadAll

transform string to a reader:

r := strings.NewReader(stringVariable)

copy all bytes from an io.Reader, and write them to an io.Writer:

n, err := io.Copy(writer, reader)
  • type assertion
var i interface{} = "hello"
s, ok := i.(string)

f, ok := i.(float64) // ok == false
  • Variadic Functions
func sum(nums ...int) {
  total := 0
  for _, num := range nums {
    total += num
  }
  fmt.Println(total)
}

nums := []int{1, 2, 3, 4}
sum(nums...)
  • Channel Directions
func ping(pings chan<- string, msg string) {
  pings <- msg
}
func pong(pings <-chan string, pongs chan<- string) {
  msg := <-pings
  pongs <- msg
}
func main() {
  pings := make(chan string, 1)
  pongs := make(chan string, 1)
  ping(pings, "passed message")
  pong(pings, pongs)
  fmt.Println(<-pongs)
}
  • select timeout pattern
c1 := make(chan string, 1)
go func() {
  time.Sleep(time.Second * 2)
  c1 <- "result 1"
}()

select {
case res := <-c1:
  fmt.Println(res)
case <-time.After(time.Second * 1):
  fmt.Println("timeout 1")
}
  • non-blocking select
select {
case msg := <-messages:
  fmt.Println("received message", msg)
default:
  fmt.Println("no message received")
}
  • b := new(bytes.Buffer)?

  • Channels

Unbuffered

Open Closed Nil
Send Blocks until the receiver is ready Panics Blocks
Receivce Blocks until there’s data available Returns zero value Blocks
Close Closes channel Panics Panics

Buffered

Open Closed Nil
Send Blocks if the channel is full Panics Blocks
Receivce Blocks until there’s data available Returns zero value Blocks
Close Closes channel Panics Panics

Senders have the ability to close the channel to notify receivers that no more data will be sent on the channel.

Receiving on a closed channel will never block - a closed channel will never block

  • example
ports := makev(chan int, 100)

You created a channel by using make(). A second parameter, an int value of 100, is provided to make() here. This allows the channel to be buffered, which means you can send it an item without waiting for a receiver to read the item. Buffered channels are ideal for maintaining and tracking work for multiple producers and consumers. You’ve capped the channel at 100, meaning it can hold 100 items before the sender will block.

  • detect a closed channel
v, ok := <-ch

if ok == false {
  fmt.Println("channel is closed")
}
  • You should always close the channel on the sender side, not the receiver side. (Except delegating quit)

Only the sender knows when the channel was closed

  • Passing pointers to channels can create race conditions

  • make

make() can only be used to initialize slices, maps, and channels. Unlike the new() function, make() does not return a pointer.

  • new

The new(T) function allocates “zeroed” storage for a new item of type T and returns its address, a value of type *T. In Go terminology, it returns a pointer to a newly allocated zero value of type T.

All 3 lines do the same:

var buf bytes.Buffer; p := &buf

p := &bytes.Buffer{}

p := new(bytes.Buffer)

Regular Expressions

// Define regexp
rthumb, err := regexp.Compile(`(?i)^.*\.(thm|lrv)$`)

if err != nil {
  log.Printf("invalid regular expression %+v", err)
  os.Exit(1)
}

if rthumb.MatchString(file.Name()) {
  fmt.Println()
}

Testing

Run tests in all sub-packages

go test ./...

Detect racing conditions:

go test -race ./...
func TestSometing(t *testing.T) {
  t.Parallel()
  ...
}

Detect racing conditions and masure the code coverage:

go test -cover -race ./...

Table-driven tests.

Fail test:

  • soft fail without a message:
t.Fail()
  • soft fail with message:
// Informs that the test breaks here
t.Errorf("got bar = %v, want %v", got, want)
  • fail the test and stop running (hard fail):
// Breaks the whole test run from here
t.Fatalf("Frobnicate(%v) returned error: %v", arg, err)

Log stuff while running go test -v:

t.Logf("iteration %v", i)

Enable parallel tests:

t.Parallel()

Skip a test:

if runtime.GOARCH == "arm" {
  t.Skip("this doesn't work on ARM")
}

Run a specific test/method (TestCoala):

go test -run Coala

Run subtests:

func TestInts(t *testing.T) {
	tt := []struct {
		name   string
		values []int
		sum    int
	}{
		{"simple sum", []int{1, 2, 3, 4}, 10},
		{"no elements", []int{}, 0},
		{"negative elements", []int{1, -1}, 0},
	}

	for _, tc := range tt {
		t.Run(tc.name, func(t *testing.T) {
			if sum := Ints(tc.values...); sum != tc.sum {
				t.Fatalf("Expected sum: %v, got: %v", tc.sum, sum)
			}
		})
	}
}
go test -v -run Ints/no_elem

Run tests for all files (wildcard):

go test github.com/user/...

Coverage details:

  • generate coverage profile file:
go test -coverprofile=cover.out

Test HTTP server:

func TestHandler(t *testing.T) {
  req, err := http.NewRequest(
    http.MethodGet,
    "http://localhost:8080/test",
    nil, // body
  )

  if err != nil {
    t.Fatalf("could not create request %v", err)
  }

  rec := httptest.NewRecorder()
  handler(rec, req)

  if rec.Code != http.StatusOK {
    t.Errorf("Expected status 200; got %d", rec.Code)
  }
}
  • show coverage statistics in terminal:
go tool cover -func=cover.out
  • open browser and show coverage statistics:
go tool cover -html=cover.out
  • generate coverage statistics as HTML file:
go tool cover -html=cover.out -o coverage.html

Errors

// To print the annotated error without the stack trace:
fmt.Printf("%v", err)
getCount failed: invalid key

// To print the stack trace as well, use %+v formatting flag
fmt.Printf("%+v", err)

Panic Reports

func reportPanics() {
  // Capture the panic if there is one, and report it.
  if panic := recover(); panic != nil {
    postToSlack(panic)
  }
}

go func() {
  // Defer reportPanics before calling myFunc --  
  // if myFunc panics, reportPanic will capture and report the panic right before
  // the goroutine exits.
  defer reportPanics()
  myFunc()
}()

NOTE The method can’t report panics for goroutines created by third-party libraries that your application uses

As HTTP middleware:

func PanicReporterMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
  defer reportPanics()
  next(w, r)
}

NOTE Add PanicReporterMiddleware as the first middleware

HTTP

Limit the amount of POST data we read:

body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))

Add middleware:

func wrapHandler(handler http.HandlerFunc) http.HandlerFunc {
	h := func(w http.ResponseWriter, r *http.Request) {
		if !userIsAuthorized(r) {
			w.WriteHeader(http.StatusUnauthorized)
			return
		}
		handler(w, r)
	}
	return h
}
...

r := mux.NewRouter()
r.HandleFunc("/user/me", wrapHandler(userHandler)).Methods("GET")

Get request header:

func handler(w http.ResponseWriter, r *http.Request) {
  userID := r.Header.Get("X-HashText-User-ID")
}

Set response code:

func handler(w http.ResponseWriter, r *http.Request) {
  w.WriteHeader(http.StatusOK)
  w.WriteHeader(http.StatusNotFound)
  w.WriteHeader(http.StatusInternalServerError)
  w.WriteHeader(http.StatusUnauthorized)
  w.WriteHeader(http.StatusBadRequest)
  // ...
}

Set response header:

func handler(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "application/json; charset=UTF-8")
}

Logging

Common log levels:

  • debug
  • trace
  • info
  • warning
  • error
  • fatal
  • panic

Create a custom logger:

var Info *log.Logger
Info = log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.LUTC|log.Llongfile)
// Or without predefining the variable
Warning := log.New(ioutil.Discard, "WARNING: ", log.Ldate|log.Ltime|log.LUTC|log.Llongfile)
Error   := log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.LUTC|log.Llongfile)

Log to a file:

// Create file with desired read/write permissions
file, err := os.OpenFile("logs/production.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
  log.Fatalln("Failed to open log file", file, ":", err)
}
defer file.Close()

// Create a new logger
Info := log.New(file, "INFO: ", log.Ldate|log.Ltime|log.LUTC|log.Llongfile)
// or use the standard one
log.SetOutput(file)

Remove standard logger prefix:

log.SetFlags(0)

Disable standard logger:

log.SetOutput(ioutil.Discard)

Set standard logger prefix:

log.SetPrefix("CUSTOM PREFIX")

Logrus

Output logs in JSON format:

import (
  log "github.com/Sirupsen/logrus"
)

func init() {
  // Log as JSON instead of the default ASCII formatter.
  log.SetFormatter(&log.JSONFormatter{})

  // Output to stdout instead of the default stderr
  // Can be any io.Writer, see below for File example
  log.SetOutput(os.Stdout)

  // Only log the warning severity or above.
  log.SetLevel(log.WarnLevel)
}

func main() {
  logger := log.WithFields(log.Fields{
    "hostname": "staging-1",
    "appname": "application-name",
    "session": session_id,
    "request_id": request_id,
    "user_ip": user_ip,
  })

  logger.Info("Log this")
}

Outputs:

{"level":"info","msg":"Redirecting user","server":"www.google.com","time":"2017-03-25T17:00:00-08:00","userId":1, ...}

Tips:

  • don’t create loggers per Gorountine
  • logging can negatively influence performance

CLI

Handle Ctrl+C:

signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT)
go func() {
	for range signals {
		log.Println("Stopping...")

    // ,,,

		return
	}
}()

or

signals := make(chan os.Signal)
signal.Notify(signals, syscall.SIGINT)
go func() {
	<-signals
	log.Println("Stopping...")
}()

Viper

Set defaults:

viper.SetDefault("port", 8080)

Read configuration file (with environment variables support):

import (
  "fmt"
  "os"

  homedir "github.com/mitchellh/go-homedir"
  "github.com/spf13/cobra"
  "github.com/spf13/viper"
)

func init() {
  // Setup viper when any command is executed
  cobra.OnInitialize(onInitialize)

  // Set some flags
  rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.cobra.yaml)")
  rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/")
  rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)")
  // ...

  viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase"))
  viper.BindPFlag("license", rootCmd.PersistentFlags().Lookup("license"))

  viper.SetDefault("license", "apache")

  // Add environment variables support
  viper.SetEnvPrefix("czerasz")
  viper.AutomaticEnv()
}

func onInitialize() {
  // Read config either from cfgFile or from home directory
  if cfgFile != "" {
    // Use configuration file from the flag
    viper.SetConfigFile(cfgFile)
  } else {
    // Find home directory.
    home, err := homedir.Dir()
    if err != nil {
      fmt.Println(err)
      os.Exit(1)
    }

    // Search config in home directory with name ".cobra" (without extension).
    viper.AddConfigPath(home)
    viper.SetConfigName(".czerasz")
  }

  if err := viper.ReadInConfig(); err != nil {
    fmt.Println("Can't read config:", err)
    os.Exit(1)
  }
}

Read configuration file with multiple environments:

prod := viper.Sub("production")

// Unmarshal into struct.
// Struct fields should match keys from config (case in-sensitive)
type config struct {
  Host    string
  Port    int
  enabled bool
}

var C config


if err := prod.Unmarshal(&C); err != nil {
    log.Fatalf("unable to decode into struct, %v", err)
}

fmt.Printf("Host: %s\n", C.Host)

Check if key is set:

if !viper.IsSet("production.port") {
  log.Fatal("missing port number")
}

Add environment variables support for nested structures:

viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

Debugging

Pprof

func main() {
  pprof.StartCPUProfile(os.Stdout)
  defer pprof.StopCPUProfile()
  ...
}
  • build the binary:
go build -o programm-name
  • generate a statistics file:
./programm-name > cpu.pprof

Or to measure the execution time:

time ./programm-name > cpu.pprof
  • open the profile file:
go tool pprof cpu.pprof

Trace:

  • add trace to your program:
func main() {
  trace.Start(os.Stdout)
  defer trace.Stop()
  ...
}
  • build the binary:
go build -o programm-name
  • generate a statistics file:
./programm-name > analisis.trace

Or to measure the execution time:

time ./programm-name > analisis.trace
  • open the analisis.trace file:
go tool trace analisis.trace

Debugging

Install Delve debugger (works with Atoms go-debug plugin):

go get -u github.com/derekparker/delve/cmd/dlv

Build and debug:

dlv debug

Build test binary and debug:

dlv test

Connect to headless debug server:

dlv connect

Set a breakpoint on the main function:

(dlv) break main.main
Breakpoint 1 set at 0x58e4c8 for main.main() ./main.go:10
(dlv) continue

or

(dlv) b main.go:10
Breakpoint 1 set at 0x58e4c8 for main.main() ./main.go:10
(dlv) c

Install package into $GOPATH/bin/$PACKAGE_NAME:

go install # inside package directory

View package information:

go list -f '{{ .Name }}: {{ .Doc }}' # inside package directory
go list -f '{{ join .Imports "\n" }}' # inside package directory

or

go list -f '{{ .Doc }}' fmt

Documentation

go doc fmt Println

Start documentation server:

godoc -http :6060

Check for errors when go run fails silently:

errcheck

Build for multiple platforms

GOOS=windows go build

See Used Go Version

go version <binary>
<binary>: go1.13.5

or

$ go version -m dlv
dlv: go1.13.5
	path	github.com/go-delve/delve/cmd/dlv
	mod	github.com/go-delve/delve	v1.3.2	h1:K8VjV+Q2YnBYlPq0ctjrvc9h7h03wXszlszzfGW5Tog=
	dep	github.com/cosiner/argv	v0.0.0-20170225145430-13bacc38a0a5	h1:rIXlvz2IWiupMFlC45cZCXZFvKX/ExBcSLrDy2G0Lp8=
...

Resource:

HTTP server performance tests

Run for 5 seconds:

go-wrk -d 5 'http://localhost:8080/test'

Useful Scripts

Get current directory:

// Get current directory (executable folder) as absolute path
// Resource: https://stackoverflow.com/a/18537419
func currentDir() string {
	ex, err := os.Executable()
	if err != nil {
		log.Fatalf("Could not get executable directory. %+v", err)
	}

	return filepath.Dir(ex)
}

Context

Basic context:

ctx := context.Background()

With possibility to cancel it:

ctx, cancel := context.WithCancel(ctx)

With timeout:

ctx, cancel := context.WithTimeout(ctx, time.Second*3)
// Uses time.AfterFunc
// Will not garbage collect before timer expires
defer cancel()

Add value to context:

ctx := context.WithValue(ctx, "variable_name", "variable value")

Scope context keyspace

type privateCtxType string

var (
  reqIDKey = privateCtxType("reqID")
)

func RequestIDValue(ctx context.Context) (id int, exists bool) {
  id, exists := ctx.Value(reqIDKey).(int)
  return
}

func WithRequestID(ctx context.Context, reqID int) context.Context {
  return context.WithValue(ctx, reqIDKey, reqID)
}

Errgroup Example

const query = "golang"

g, ctx := errgroup.WithContext(ctx)

searches := []Search{Web, Image, Video}
results := make([]Result, len(searches))
for i, search := range searches {
  i, search := i, search // https://golang.org/doc/faq#closures_and_goroutines
  g.Go(func() error {
    result, err := search(ctx, query)
    if err == nil {
      results[i] = result
    }
    return err
  })
}

if err := g.Wait(); err != nil {
  fmt.Fprintln(os.Stderr, err)
  return
}

for _, result := range results {
  fmt.Println(result)
}

HTTP request example

package main

import (
	"context"
	"fmt"
	"log"
	"math"
	"net/http"
	"time"
)

func main() {
	// Context setup
	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	go func ()  {
		dur := time.Second*10
		time.Sleep(dur)
		log.Printf("Canceled request after: %.2f seconds\n", dur.Seconds())
		cancel()
	}()

	// Prepare a request which takes 5 seconds
	slowURL := "http://www.mocky.io/v2/5185415ba171ea3a00704eed?mocky-delay=5s"
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, slowURL, http.NoBody)
	if err != nil {
		log.Printf("Problem with request. %s\n", err)
		return
	}

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Printf("Problem with response. %s\n", err)
		return
	}
	
	if end, ok := ctx.Deadline(); ok {
		fmt.Printf("Remain: %.2f seconds\n", math.Abs(time.Now().Sub(end).Seconds()))
	}
	fmt.Printf("Response status: %d\n", res.StatusCode)
}
$ go run http-with-context.go
2020/04/28 15:28:56 Problem with response. Get http://www.mocky.io/v2/5185415ba171ea3a00704eed?mocky-delay=5s: context deadline exceeded

Resources

Tags

package main

import (
	"fmt"
	"reflect"
)

type T struct {
    f1 string `one:"1" two:"2" blank:""`
    f2 string "f one"
    f3 string "invalid:``" // Only double quotes are allowed
}

func main() {
    t := reflect.TypeOf(T{})
    f, _ := t.FieldByName("f1")
    fmt.Println(f.Tag) // one:"1" two:"2" blank: 4

    v, ok := f.Tag.Lookup("one")
    fmt.Printf("%s, %t\n", v, ok) // 1, true

    // Alternatively use get
    v = f.Tag.Get("one")
    fmt.Printf("%s\n", v) // 1

    v, ok = f.Tag.Lookup("blank")
    fmt.Printf("%s, %t\n", v, ok) // , true

    v, ok = f.Tag.Lookup("five")
    fmt.Printf("%s, %t\n", v, ok) // , false
}

Example:

type Member struct {
    Age int `json:"age,string" xml:"the_age,string"`
}

Resources:

Go Docker Image

FROM golang
COPY . /go/src/github.com/czerasz/example
WORKDIR /go/src/github.com/czerasz/example
RUN go get && CGO_ENABLED=0 GOOS=linux go build -o server .

FROM scratch
LABEL maintainer="Michal Czeraszkiewicz <contact@czerasz.com>"
COPY --from=0 /go/src/github.com/czerasz/example/server /opt/czerasz/vision/server
COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
ADD html/ /opt/czerasz/vision/html
EXPOSE 8080
WORKDIR /opt/czerasz/vision
ENTRYPOINT [ "./server" ]

Resources:

Reflect Package - Type

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var v float64 = 65.5
	getTypeInfo(v)
}

func getTypeInfo(v interface{}) {
	fmt.Printf("Type via Printf: %T\n", v)

	vo := reflect.ValueOf(v)
	fmt.Printf("ValueOf: %v\n", vo)
	fmt.Printf("ValueOf().Type(): %v\n", vo.Type())

	to := reflect.TypeOf(v)
	fmt.Printf("TypeOf: %v\n", to)
}
$ go run reflect-example.go
Type via Printf: float64
ValueOf: 65.5
ValueOf().Type(): float64
TypeOf: float64

Resources:


Modules

Initialize module:

go mod init github.com/czerasz/example

The above command creates go.mod file

  • install specific module
# specific branch
go get github.com/joho/godotenv@master
# specific tag
go get github.com/joho/godotenv@v1.2.0
# specific commit
go get github.com/joho/godotenv@d6ee687

The above command creates or updates go.sum file

  • clean up
go mod tidy 

Update the go.mod file - remove unused dependencies and add any missing ones This command should be used after each commit.

  • update minor or patch version
go get -u gopkg.in/gookit/color.v1
go get -u=patch gopkg.in/gookit/color.v1
  • update major version

semantic import versions - this feature allows go to use two versions of the package at the same time

import (
    "github.com/urfave/cli/v2"
)
  • vendoring
$ go mod tidy
$ go mod vendor

Creates a vendor directory and vendor/modules.txt file

  • replace module:
go mod edit -replace=github.com/golangci/golangci-lint=github.com/khan/golangci-lint@${LATEST_GOLANGCI_COMMIT}
# This will create/update the replace directive to github.com/Khan/golangci-lint v1.27.2-0.20200610182323-d6b2676ecc9b
# One could also have used the branch name instead of commit SHA1.

Or use local version:

go mod edit -replace github.com/czerasz/bar=/home/czerasz/Projects/bar

Resources:


Tools

Including Static Files Into Go Binaries


Binary Size

UPX usage:

curl -sSLO https://github.com/upx/upx/releases/download/v3.96/upx-3.96-amd64_linux.tar.xz
tar -xf upx-3.96-amd64_linux.tar.xz
./upx-3.96-amd64_linux/upx --brute out

Resources:


Editor

Useful Atom packages:

  • autocomplete-go
  • language-protobuf
  • platformio-ide-terminal - Atom terminal
  • go-debug
  • go-plus
  • gomodifytags - find package name

Improve Go development in Atom:

  • Packages → Go → Update Tools

  • Generating Ctags for your Go code - works with Atoms autocomplete:

    Install: go get -u github.com/jstemmer/gotags Generate tags: gotags -tag-relative=true -R=true -sort=true -f="tags" -fields=+l .


Ctrl+C

Resources:


  • $GOROOT/src/pkg/net/http/internal can be imported only by the standard net/http and net/http/* packages.
  • $GOPATH/src/mypkg/internal/foo can be imported only by code in $GOPATH/src/mypkg.

Resources

Blogs worth reading:

structuring applications in GO plurasight.com - greating wed applications in GO golang-book.com