Golang 101
Who am I?
Damiano Petrungaro
Italy
source: www.vidiani.com
Tivoli (RM)
source: www.livitaly.com
Tivoli (RM)
source: www.romaest.org
Tivoli (RM)
source: www.tivolitouring.com
Tivoli (RM)
source: www.confinelive.it
Tivoli (RM)
source: www.greenparkmadama.it
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?
- It's easy to learn
- It has amazing performance
- It's concurrent
- It has big use cases
- Built by Google
Let's get dirty!
- Variables and constants
- Statements and functions
- Pointers and values
- Structs
- Interfaces
- Error handling
- Packaging and naming
- Package management
- Async
- Tests and benchmarks
- 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
-
Use fewer chars as possible for naming
-
How to_name VARIABLES in-the-Code
-
A folder structure
-
Packages should not depend on other packages
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
- go fmt
- go vet
- golint
- godoc
- goimport
Links
- https://dmitri.shuralyov.com/idiomatic-go
- https://talks.golang.org/2014/readability.slide
- https://golang.org/doc/effective_go.html
- https://go-proverbs.github.io
- https://www.ardanlabs.com/
- https://learning.oreilly.com/videos/ultimate-go-programming/9780134757476
- https://www.youtube.com/watch?v=WkzGQ-Sm_50