Golang 101

Who am I?

Damiano Petrungaro

Italy

source: www.vidiani.com

Tivoli (RM)

Tivoli (RM)

source: www.romaest.org

Tivoli (RM)

Tivoli (RM)

Tivoli (RM)

Me everyday:

Go(lang)

Go, also known as Golang is a statically typed, compiled programming language designed at Google.

Go is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency.

Why Go?

  1. It's easy to learn
  2. It has amazing performance
  3. It's concurrent
  4. It has big use cases
  5. Built by Google

Let's get dirty!

  1. Variables and constants
  2. Statements and functions
  3. Pointers and values
  4. Structs
  5. Interfaces
  6. Error handling
  7. Packaging and naming
  8. Package management
  9. Async
  10. Tests and benchmarks
  11. Go tools

Before we start...

Variables and constants

Constants

package main

import (
	"regexp"
)

const globalConstant = "a global string"
const r = regexp.MustCompile("") // const initializer regexp.MustCompile("") is not a constant

func main() {
	const name string = "Golang"
	const version float64 = 1.13
	const major int = 1
	// more code here ...
}

// With type inference
func main() {
	const name = "Golang"
	const version = 1.13
	const major = 1
	// more code here ...
}

Variables

package main

import "regexp"

var globalVariable = "a global string"
var aGlobalRegex = regexp.MustCompile("...")

func main() {
	var name string = "Golang"
	var version float64 = 1.13
	var major int = 1
	// more code here ...
}

// With type inference
func main() {
	var name = "Golang"
	var version = 1.13
	var major = 1
	// more code here ...
}

// With short declaration
func main() {
	name := "Golang"
	version := 1.13
	major := 1
	// more code here ...
}

// With zero-value
func main() {
	var name string     // ""
	var version float64 // 0.0
	var major int       // 0
	// more code here ...
}

Types

package main

// string
// bool
// int8
// uint8(byte)
// int16
// uint16
// int32(rune)
// uint32
// int64
// uint64
// int
// uint
// uintptr
// float32
// float64
// complex64
// complex128

Types

package main

import "fmt"

// As zero-value
func main() {
	var anArray [1]int
	var aSlice []int
	var aMap map[string]int
}

// With composite literal
func main() {
	anArray := [1]int{2}
	aSlice := []string{"value"}
	aMap := map[string]int{"age": 20}
}

// With length and capacity
func main() {
	var aSlice = make([]string, 2, 10)
	var anotherSlice = new([]string)
}

// What's inside?
func main() {
	var aSlice = make([]string, 4, 5)
	var anEmptySlice = make([]string, 0, 10)
	var aPointerToSlice = new([]string)

	fmt.Printf("%#v\n", aSlice)          // []string{"", "", "", ""}
	fmt.Printf("%#v\n", anEmptySlice)    // []string{}
	fmt.Printf("%#v\n", aPointerToSlice) // &[]string(nil)
}

Types

package main

import "fmt"

type username string
type printUsernameFunc func(username) bool

func printUsername(u username) bool {
	// print the username
	return true
}

func main() {
	mario := username("mario")
	printUsername(mario)
	printUsername("luigi")
}

func main() {
	complexfunction(printUsername)
}

func complexfunction(fn printUsernameFunc) {
	mario := username("mario")
	ok := fn(mario)
	fmt.Println(ok)
	ok = fn("luigi")
	fmt.Println(ok)
}

Statements and functions

If

package main

// A simple if statement
func main() {
	a := 1
	if a > 10 {
		// Do something
	}
	// Continue here
}

// An if/else statement
func main() {
	i := len("Mario")
	if i < 10 {
		// Do something
	} else if i > 10 {
		// Do something
	} else {
		// Do something
	}
}

// A better if/else statement
func main() {
	i := len("Mario")
	if i < 10 {
		// Do something
		return
	}
	if i > 10 {
		// Do something
		return
	}
	// Do something
}

Switch

package main

// A simple switch statement
func main() {
	name := "Mario"
	switch name {
	case "Mario":
		// Do something
	case "Luigi":
		// Do something
	default:
		// Do something
	}
}

// A simple switch statement with assignment
func main() {
	var name interface{}
	name = "Mario"
	switch t := name.(type) {
	case string:
		// Do something
	case int:
		// Do something
	default:
		// Do something
	}
}

For loop... let's guess!

For loop

package main

// A simple for loop
func main() {
	for i := 0; i > 10; i++ {
		// Do something
	}
}

For each loop... let's guess!

For each loop

package main

// A simple for loop (again)
func main() {
	list := []int{1, 10, 234, 63, 32, 8, 24, 8}
	for i, v := range list {
		// Do something
	}
}

// A simple for loop (again) ignoring the index
func main() {
	list := []int{1, 10, 234, 63, 32, 8, 24, 8}
	for _, v := range list {
		// Do something
	}
}

While loop... let's guess!

While loop

package main

// A simple for loop (again)
func main() {
	stop := false
	for !stop {
		// Do something
		stop = true
	}
}

Functions

package main

import (
	"fmt"
	"os/exec"
)

// A simple function
func funcName() {
	// Do something
}

// With a value
func funcName(i int) {
	// Do something
}

// With three integers
func funcName(a, b, c int) {
	// Do something
}

// With an integer and a string
func funcName(i int, s string) {
	// Do something
}

// With a return type
func funcName(i int, s string) bool {
	return false
}

// With n return types :wat:
func funcName(i int, s string) (string, bool) {
	return "", true
}

// With an error
func funcName(i int, s string) (string, error) {
	return "", exec.ErrNotFound
}

// With named return values
func funcName() (name string, surname string, nickname string) {
	return
}

// With a named return value changed
func funcName() (name string, surname string, nickname string) {
	name = "Mario"
	return
}

// Lambda
func main() {
	a := func(name string) {
		fmt.Println(name)
	}

	a("Mario")
	a("Luigi")

	func(name string) {
		fmt.Println(name)
	}("Peach")
}

Defer

package main

import "fmt"

func main() {
	defer printZero()
	printOne()
	printTwo()
}

func printZero() {
	fmt.Println(0)
}

func printOne() {
	fmt.Println(1)
}

func printTwo() {
	fmt.Println(2)
}

Pointers and Values

Syntax

package main

import (
	"fmt"
)

func main() {
	var s2 = new(string)
	*s2 = "another value"
	print(s2)
}

func print(s *string) {
	fmt.Println(s)
}

Values

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	in10Seconds := now.Add(10 * time.Second)

	fmt.Println(now.Unix(), in10Seconds.Unix())
}

func main() {
	now := time.Now()
	in10Seconds := addTenSeconds(now)

	fmt.Println(now.Unix(), in10Seconds.Unix())
}

func addTenSeconds(t time.Time) time.Time {
	return t.Add(10 * time.Second)
}

Pointers

package main

import (
	"fmt"
	"net/http"
)

func main() {
	const url = "http://google.com"
	req, err := http.NewRequest(http.MethodGet, "http://google.com", nil)
	if err != nil {
		fmt.Println("an error occurred", err)
		return
	}

	fmt.Println(req.URL.String())
}

func main() {
	const url = "http://google.com"
	req, err := NewRequest(http.MethodGet, "http://google.com")
	if err != nil {
		fmt.Println("an error occurred", err)
		return
	}

	fmt.Println(req.URL.String())
}

func NewRequest(method, url string) (*http.Request, error) {
	return http.NewRequest(http.MethodGet, url, nil)
}

Value or pointer?

Think about the usage

Think about the performance

Show me the benchmarks!

Stack or heap?

Structs

Structs ARE NOT objects

 

Structs

package main

import (
	"fmt"
	"time"
)

type user struct {
	firstName string
	lastName  string
	birthDate birthDate
}

type birthDate struct {
	t time.Time
}

func main() {
	u := user{
		firstName: "Mario",
		lastName:  "Super",
		birthDate: birthDate{
			time.Now(),
		},
	}

	fmt.Println(u.firstName, u.lastName, u.birthDate.t.Unix())
}

// Without keys
func main() {
	u := user{
		"Mario",
		"Super",
		birthDate{
			time.Now(),
		},
	}

	fmt.Println(u.firstName, u.lastName, u.birthDate.t.Unix())
}


// With zero values
func main() {
	u := user{}

	fmt.Println(u.firstName, u.lastName, u.birthDate.t.Unix())
}

Structs (with pointer)

package main

import (
	"fmt"
	"time"
)

type user struct {
	firstName string
	lastName  string
	birthDate *birthDate
}

type birthDate struct {
	t time.Time
}

func main() {
	u := user{
		firstName: "Mario",
		lastName:  "Super",
		birthDate: &birthDate{
			time.Now(),
		},
	}

	fmt.Println(u.firstName, u.lastName, u.birthDate.t.Unix())
}

// Without keys
func main() {
	u := user{
		"Mario",
		"Super",
		&birthDate{
			time.Now(),
		},
	}

	fmt.Println(u.firstName, u.lastName, u.birthDate.t.Unix())
}


// With zero values
func main() {
	u := user{}
	
    // panic: runtime error: invalid memory address or nil pointer dereference
	fmt.Println(u.firstName, u.lastName, u.birthDate.t.Unix())
}

Factories

JAVA?

Used to build structs

Factory function

package main

import "time"

type userName struct {
	v string
	t time.Time
}

func new(v string) userName {
	return userName{
		v, time.Now(),
	}
}

func newWithCreationDate(v string, t time.Time) userName {
	return userName{v, t}
}

Methods

package main

import (
	"fmt"
)

type superHero struct {
	name  string
	power string
}

func new(name, power string) superHero {
	return superHero{name, power}
}

func (s superHero) action() {
	fmt.Printf("the super hero %s uses %s", s.name, s.action)
}

func (s superHero) mutation(name, power string) superHero {
 	return new(name, power)
}

func main() {
	superman := new("Superman", "laser eyes")
	superman.action()
	supermanRage := superman.mutation("Superman - rage mode", "super laser eyes")
	superman.action()
	supermanRage.action()
}

value receiver

Methods

package main

import "fmt"

type user struct {
	id   int
	name string
}

func new(id int, name string) *user {
	return &user{id, name}
}

func (u *user) rename(name string) {
	u.name = name
}

func main() {
	u := new(1, "Mario")
	fmt.Println(u.name)
	u.rename("Luigi")
	fmt.Println(u.name)
}

pointer receiver

Embedding

package main

import "fmt"

type user struct {
	name string
}

type admin struct {
	user
	role string
}

func main() {
	u := user{"Mario"}
	a := admin{user{"Luigi"}, "CTO"}

	fmt.Println(u.name)
	fmt.Println(a.name, a.role)
}

Interfaces

Define an interface

package main

import "fmt"

type printer interface {
	print(s string)
}

Implement an interface

package main

import "fmt"

type printer interface {
	print(s string)
}

type fmtPrinter struct {
}

func (p *fmtPrinter) print(s string) {
	fmt.Println(s)
}

func main() {
	p := &fmtPrinter{}
	printerFunc("Mario", p)
}

func printerFunc(s string, p printer) {
	p.print(s)
}

Multiple interfaces

package main

import "fmt"

type printer interface {
	print(s string)
}

type reader interface {
	read(s string)
}

type printerReader interface {
	printer
	reader
}

type fmtPrinterReader struct {
	s string
}

func (p *fmtPrinterReader) print(s string) {
	fmt.Println(fmt.Sprintln(p.s, s))
}

func (p *fmtPrinterReader) read(s string) {
	p.s = fmt.Sprintln(p.s, s)
}

func main() {
	p := &fmtPrinterReader{}
	printerFunc("Super", "Mario", p)
}

func printerFunc(read, print string, p printerReader) {
	p.read(read)
	p.print(print)
}

The empty interface

package main

import "fmt"

func main() {
	printValue("Mario")
	printValue(10)
	printValue(struct{}{})
	printValue(map[string]string{"Man": "Mario", "Woman": "Peach"})
}

func printValue(v interface{}) {
	fmt.Println(v)
}

Check if struct can be used as interface

package main

type printer interface {
	print(s string)
}

type fmtPrinter struct {
}

var _ printer = (*fmtPrinter)(nil)

// func (p *fmtPrinter) print(s string) {
// 	fmt.Println(s)
// }

Do you really need an interface?

Errors

What is an error?

An error is a value.
it is not an exception.

An error is an interface

package builtin


// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}

An error

package main

import "errors"

var errUserNotFound = errors.New("main: user not found")

func main() {
	var errUserNotFound = errors.New("main: user not found")
	errUserNotLogged := errors.New("main: user not logged")
}

How to "try/catch"

package main

import (
	"errors"
	"fmt"
	"strconv"
)

func stringLength(s string) (int, error) {
	i, err := strconv.Atoi(s)
	if err != nil {
		return 0, errors.New("main: an error occurred transforming string to integer")
	}

	return i, nil
}

// One-line idiomatic error check
func main() {
	if _, err := stringLength("Mario"); err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("Job done!")
}

// Keep in mind the scope of the "if"
func main() {
	i, err := stringLength("Mario")
	if err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println(i)
}

Adding details to the error

package main

import (
	"fmt"
	"github.com/pkg/errors"
	"strconv"
)

// Wrapping errors
func stringLength(s string) (int, error) {
	i, err := strconv.Atoi(s)
	if err != nil {
		return 0, errors.Wrap(err, "main: an error occurred transforming string to integer")
	}

	return i, nil
}

func main() {
	i, err := stringLength("Mario")
	switch err := errors.Cause(err).(type) {
	case *strconv.NumError:
		fmt.Println(err)
	default:
		fmt.Println(i)
	}
}

use carefully!

Panic!

package main

import (
	"fmt"
	"github.com/pkg/errors"
	"strconv"
)

func main() {
	panic("ops!")
	i, err := strconv.Atoi("Mario")
	switch err := errors.Cause(err).(type) {
	case *strconv.NumError:
		fmt.Println(err)
	default:
		fmt.Println(i)
	}
}

Do not use it! (or use it where it makes sense)

Use os.Exit()

package main

import (
	"fmt"
	"github.com/pkg/errors"
	"os"
	"strconv"
)

func main() {
	os.Exit(0)
	i, err := strconv.Atoi("Mario")
	switch err := errors.Cause(err).(type) {
	case *strconv.NumError:
		fmt.Println(err)
	default:
		fmt.Println(i)
	}
}

Packaging and conventions

What is a package?

What is a package?

A package is a bounded context.

All the structures and functions listed inside, are placed to solve a specific problem.

Conventions

  1. Use fewer chars as possible for naming

  2. How to_name VARIABLES in-the-Code

  3. A folder structure

  4. Packages should not depend on other packages

1

Package management

$ go mod

$ go mod init

 initialize new module in current directory

$ go mod tidy

 add missing and remove unused modules

$ go mod vendor

make vendored copy of dependencies

$ go mod download

download modules to local cache

Async in Go!

Do you really need async?

Tests and Benchmarks

Tooling

Links