Skip to content
English
On this page

GO

Hello World

File hello.go:

go
package main

import "fmt"

func main() {
    fmt.Println("Hello Go")
}

$ go run hello.go

Declarations

  • Type goes after identifier!
  • var foo int // declaration without initialization
  • var foo int = 42 // declaration with initialization
  • var foo, bar int = 42, 1302 // declare and init multiple vars at once
  • var foo = 42 // type omitted, will be inferred
  • foo := 42 // shorthand, only in func bodies, omit var keyword, type is always implicit
  • const constant = "This is a constant"

Functions

go
// a simple function
func functionName() {}

// function with parameters (again, types go after identifiers)
func functionName(param1 string, param2 int) {}

// multiple parameters of the same type
func functionName(param1, param2 int) {}

// return type declaration
func functionName() int {
    return 42
}

// Can return multiple values at once
func returnMulti() (int, string) {
    return 42, "foobar"
}
var x, str = returnMulti()

// Return multiple named results simply by return
func returnMulti2() (n int, s string) {
    n = 42
    s = "foobar"
    // n and s will be returned
    return
}
var x, str = returnMulti2()

Functions As Values And Closures

go
func main() {
    // assign a function to a name
    add := func(a, b int) int {
        return a + b
    }
    // use the name to call the function
    fmt.Println(add(3, 4))
}

// Closures: Functions can access values that were in scope when defining the
// function

// adder returns an anonymous function with a closure containing the variable sum
func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x // sum is declared outside, but still visible
        return sum
    }
}

Variadic Functions

go
func main() {
  fmt.Println(adder(1, 2, 3)) // return 6
  fmt.Println(adder(9, 9)) // return 18
}

// By using ... before the type name of the last parameter you can indicate that it takes zero or more of those parameters.
// The function is invoked like any other function except we can pass as many arguments as we want.
func adder(args ...int) int {
  total := 0
  for _, v := range args { // Iterates over the arguments whatever the number.
    total += v
  }
  return total
}

Built-in Types

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32 ~= a character (Unicode code point) - very Viking

float32 float64

complex64 complex128

Type Conversions

go
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

// alternative syntax
i := 42
f := float64(i)
u := uint(f)

Packages

  • package declaration at top of every source file
  • executables are in package main
  • convention: package name == last name of import path (import path math/rand => package rand)
  • upper case identifier: exported (visible from other packages)
  • Lower case identifier: private (not visible from other packages)

Control structures

If

go
if x > 0 {
        return x
    } else {
        return -x
    }

    // You can put one statement before the condition
    if a := b + c; a < 42 {
        return a
    } else {
        return a - 42
    }

Loops

go
// There's only `for`, no `while`, no `until`
    for i := 1; i < 10; i++ {
    }
    for ; i < 10;  { // while - loop
    }
    for i < 10  { // you can omit semicolons if there is only a condition
    }
    for { // you can omit the condition ~ while (true)
    }

Switch

go
// switch statement
    switch operatingSystem {
    case "darwin":
        fmt.Println("Mac OS Hipster")
        // cases break automatically, no fallthrough by default
    case "linux":
        fmt.Println("Linux Geek")
    default:
        // Windows, BSD, ...
        fmt.Println("Other")
    }

    // as with for and if, you can have an assignment statement before the switch value 
    switch os := runtime.GOOS; os {
    case "darwin": ...
    }

Arrays, Slices, Ranges

Arrays

go
var a [10]int // declare an int array with length 10. Array length is part of the type!
a[3] = 42     // set elements
i := a[3]     // read elements

// declare and initialize
a := [2]int{1, 2}
a := [...]int{1, 2} // elipsis -> Compiler figures out array length

Slices

go
var a []int // declare a slice - similar to an array, but length is unspecified
var a = []int {1, 2, 3, 4} // declare and initialize a slice (backed by the array given implicitly)
a := []int{ 1, 2, 3, 4 } // shorthand


var b = a[lo:hi] // creates a slice (view of the array) from index lo to hi-1
var b = a[1:4] // slice from index 1 to 3
var b = a[:3] // missing low index implies 0
var b = a[3:] // missing high index implies len(a)

// create a slice with make
a = make([]byte, 5, 5) // first arg length, second capacity
a = make([]byte, 5) // capacity is optional

// create a slice from an array
x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // a slice referencing the storage of x

Operations on Arrays and Slices

len(a) gives you the length of an array/a slice. It's a built-in function, not a attribute/method on the array.

go
// loop over an array/a slice
for i, e := range a {
    // i is the index, e the element
}

// if you only need e:
for _, e := range a {
    // e is the element
}

// ...and if you only need the index
for i := range a {
}

// In Go pre-1.4, you'll get a compiler error if you're not using i and e.
// Go 1.4 introduced a variable-free form, so that you can do this
for range time.Tick(time.Second) {
    // do it once a sec
}

Maps

go
var m map[string]int
m = make(map[string]int)
m["key"] = 42
fmt.Println(m["key"])

delete(m, "key")

elem, ok := m["key"] // test if key "key" is present and retrieve it, if so

// map literal
var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

Structs

There are no classes, only structs. Structs can have methods.

go
// A struct is a type. It's also a collection of fields 

// Declaration
type Vertex struct {
    X, Y int
}

// Creating
var v = Vertex{1, 2}
var v = Vertex{x: 1, y: 2} // Creates a struct by defining values with keys 

// Accessing members
v.X = 4

// You can declare methods on structs. The struct you want to declare the
// method on (the receiving type) comes between the the func keyword and
// the method name. The struct is copied on each method call(!)
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// Call method
v.Abs()

// For mutating methods, you need to use a pointer (see below) to the Struct
// as the type. With this, the struct value is not copied for the method call.
func (v *Vertex) add(n float64) {
    v.X += n
    v.Y += n
}

Pointers

go
p := Vertex{1, 2}  // p is a Vertex
q := &p            // q is a pointer to a Vertex
r := &Vertex{1, 2} // r is also a pointer to a Vertex

// The type of a pointer to a Vertex is *Vertex

var s *Vertex = new(Vertex) // new creates a pointer to a new struct instance

Interfaces

go
// interface declaration
type Awesomizer interface {
    Awesomize() string
}

// types do *not* declare to implement interfaces
type Foo struct {}

// instead, types implicitly satisfy an interface if they implement all required methods
func (foo Foo) Awesomize() string {
    return "Awesome!"
}

Methods

  • A method is a function with a receiver.
  • The receiver of a method is a special parameter.
  • The receiver is not listed in the parameter list but before the method name
  • A method can have only one receiver.
  • The receiver has a type T or T*
    • When the receiver has a type T, we say that it’s a “value receiver”
    • When it has a type T*, we say that it’s a “pointer receiver”
  • We say that the base type is T

methods

go
package main

import (
    "os/user"
    "time"

    "github.com/Rhymond/go-money"
)

type Item struct {
    ID string
}

type Cart struct {
    ID        string
    CreatedAt time.Time
    UpdatedAt time.Time
    lockedAt  time.Time
    user.User
    Items        []Item
    CurrencyCode string
    isLocked     bool
}

func (c *Cart) TotalPrice() (*money.Money, error) {
    // ...
    return nil, nil
}

func (c *Cart) Lock() error {
    // ...
    return nil
}

We have defined the type Cart.

This type has two methods: TotalPrice

Lock Those two methods are functions.

  • They are bound to the type Cart.
  • The receiver for the method TotalPrice is called c and is of type *Cart
  • The receiver for the method Lock is called c and is of type *Cart

Methods are capabilities

With methods, you can give additional capabilities to the cart Type. In the previous example, we add the capability for somebody that manipulate a Cart to :

  • Lock the cart
  • Compute the total price

Method Names

  • Method names should be unique inside a method set.

  • What is a method set?

    • The method set of a type T is the group of all methods with a receiver T
    • The method set of a type *T is the group of all methods with a receiver T and *T

It means that you CANNOT have two methods with the same name, even if the first one has a receiver type and the second one has a value type :

How to call methods

Methods are called with the “dot notation”. The receiver argument is passed to the method with a dot.

go
package main

import (
    "call/cart"
    "log"
)

func main() {
    // load the cart... into variable cart
    newCart := cart.Cart{}

    totalPrice, err := newCart.TotalPrice()
    if err != nil {
        log.Printf("impossible to compute price of the cart: %s", err)
        return
    }
    log.Println("Total Price", totalPrice.Display())

    err = newCart.Lock()
    if err != nil {
        log.Printf("impossible to lock the cart: %s", err)
        return
    }

}
  • In the previous example, the methods TotalPrice and Lock are called (with the dot notation)

totalPrice, err := cart.TotalPrice(), here we pass the variable cart to the method TotalPrice

err = cart.Lock(), here we pass the variable cart to the method Lock

  • Those two methods are bound to the type Cart from the current package (main)

  • We also call the method Display bound to the type Money from the package money

  • Note that the package cart belongs to the module “call”

Should I use a pointer receiver or a value receiver?

When you use a value receiver the data will be copied internally before the method is executed. The method will use a copy of the variable. This has two consequences :

  1. A method with a value receiver cannot modify the data passed to it

  2. The internal copy process might impact your program’s performance (most of the time, it’s negligible, except for heavy type structs).

With a pointer receiver, the data passed to it can be modified by the method.

Receiver type and method call

The receiver is an additional function parameter.

Methods receivers are either pointer receivers or value receivers.

In this method, the receiver has the type *Cart (a pointer to Cart).

go
func (c *Cart) TotalPrice() (*money.Money, error) {
    // ...
    return total, nil
}

When we call this method, we use the following notation :

go
newCart := cart.Cart{}
totalPrice, err := newCart.TotalPrice()
//...

Did you notice something weird?

  • We have learned that a function parameter has a type. This type should be respected (you cannot give a function an uint8 if it expects a string).
  • The type of newCart is Cart (from cart package)
  • The type of the receiver is *Cart.
  • Types are not the same!

Should it trigger an error, no? It does not. Why?

  • Go will convert the variable newCart automatically to a pointer.

Rules

  • Methods with pointer receivers can take a pointer OR a value as receiver
  • Methods with value receivers can take a pointer OR a value as receiver

methods

Impact of Receivers types on method calling

Methods visibility

Methods like functions have a visibility.

When the first letter of the method name is capitalized, the method is exported.

In the previous example, we put all our code into the main package; consequently, method visibility did not matter much. However, when you create a package, you must consider methods’ visibility.

  • An exported method is callable outside the package.
  • A non exported method is NOT callable outside the package.

Type Embedding

TODO

Errors

There is no exception handling. Functions that might produce an error just declare an additional return value of type Error. This is the Error interface:

go
type error interface {
    Error() string
}

A function that might return an error:

go
func doStuff() (int, error) {
}

func main() {
    result, error := doStuff()
    if (error != nil) {
        // handle error
    } else {
        // all is good, use result
    }
}

Concurrency

Goroutines

Goroutines are lightweight threads (managed by Go, not OS threads). go f(a, b) starts a new goroutine which runs f (given f is a function).

go
// just a function (which can be later started as a goroutine)
func doStuff(s string) {
}

func main() {
    // using a named function in a goroutine
    go doStuff("foobar")

    // using an anonymous inner function in a goroutine
    go func (x int) {
        // function body goes here
    }(42)
}

Channels

go
ch := make(chan int) // create a channel of type int
ch <- 42             // Send a value to the channel ch.
v := <-ch            // Receive a value from ch

// Non-buffered channels block. Read blocks when no value is available, write blocks if a value already has been written but not read.

// Create a buffered channel. Writing to a buffered channels does not block if less than <buffer size> unread values have been written.
ch := make(chan int, 100)

close(c) // closes the channel (only sender should close)

// read from channel and test if it has been closed
v, ok := <-ch

// if ok is false, channel has been closed

// Read from channel until it is closed
for i := range ch {
    fmt.Println(i)
}

// select blocks on multiple channel operations, if one unblocks, the corresponding case is executed
func doStuff(channelOut, channelIn chan int) {
    select {
    case channelOut <- 42:
        fmt.Println("We could write to channelOut!")
    case x := <- channelIn:
        fmt.Println("We could read from channelIn")
    }
}

Snippets

HTTP Server

go
package main

import (
    "fmt"
    "net/http"
)

// define a type for the response
type Hello struct{}

// let that type implement the ServeHTTP method (defined in interface http.Handler)
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

func main() {
    var h Hello
    http.ListenAndServe("localhost:4000", h)
}

// Here's the method signature of http.ServeHTTP:
// type Handler interface {
//     ServeHTTP(w http.ResponseWriter, r *http.Request)
// }

Go Modules

bash
go mod init yourModulePath
  • Cleanup your go.mod and go.sum file
bash
go mod tidy
  • Print the dependency graph of your module
bash
go mod graph
  • Create a vendor directory to store all your dependencies
bash
go mod vendor
  • Check that the dependencies downloaded locally have not been altered
bash
go mod verify
  • List all dependencies along with their version
bash
go list -m all

Environment variables

  • To check if an environment variable exists, you can use the os package :
go
port, found := os.LookupEnv("DB_PORT")
if !found {
    log.Fatal("impossible to start up, DB_PORT env var is mandatory")
}