Skip to content

Instantly share code, notes, and snippets.

@peltho
Last active May 11, 2024 18:18
Show Gist options
  • Save peltho/610d7177d9c035dda14f69d49005d2e5 to your computer and use it in GitHub Desktop.
Save peltho/610d7177d9c035dda14f69d49005d2e5 to your computer and use it in GitHub Desktop.
Golang cheatsheet

Golang cheatsheet

Variables

var foo int
var foo = 0
foo := 0

var a, b, c int = 1, 2, 3

Arrays

var foo [5]int
var multiFoo [2][3]int

Slices

var foo []int
foo := []int{1, 2, 3, 4}
foo := make([]int, 10)
foo = append(foo, 5)

foo[2:] // 3, 4
foo[:3] // 1, 2, 3

Maps

var m map[string]int
m := make(map[string]int)

m["someKeyString"] = 0

Hint: Slices and Maps use internal pointers, so copies point to same underlying data

Conditional statements

if foo := 9; foo < 0 {
    fmt.Println(foo, "is negative") // not displayed
} else if foo < 10 {
    fmt.Println(foo, "has 1 digit") // displayed
} else {
    fmt.Println(foo, "has multiple digits") // not displayed
}

Functions

func foo(a int, b int) int {
    c := a + b
    return c
}

// Same thing:

func foo(a int, b int) (c int) {
    c = a + b
    return
}

Combining all above:

import (
    "fmt"
    "strings"
)

func upCaseAll(str ...string) { // dynamic number of arguments
    for _, s := range str {  // range also returns key and value (use _ to forget about the key)
        fmt.Println(strings.ToUpper(s))
    }
}

func main() {
    upCaseAll("Hello", "world")
    upCaseAll("Hello")
    aSlice := []string{"Hello", "World"}
    upCaseAll(aSlice...)
}

Structs

type person struct {
    name   string
    age       int
    gender string
}

p = person{name: "Thomas", age: 28, gender: "Male"}
p = person{"Thomas", 28, "Male}

p.name // Thomas
p.gender // Male

Error handling

func main() {
    resp, err := http.Get("http://example.com/")
    if err != nil {
        fmt.Println(err)
        return
    }
    return fmt.Println(resp)
}

Methods

Go doesn't have classes. You have to define methods on types. A method is a func with a special receiver argument

type person struct {
    name string
    age     int
}

func (p person) getName() string {
    return p.name
}

func main() {
    v := person{"Thomas", 28}
    fmt.Println(v.getName()) // Prints Thomas
    fmt.Println(v.name) // Prints Thomas too
}

Pointer receivers (most common case)

Methods with pointer receivers can modify the value to which the receiver points. In other languages you should have done (*p).name for this to work

func (p *person) setName(s string) string {
    p.name = s
    return p.name
}

func main() {
    v := person{"Thomas", 28}
    fmt.Println(v.setName("Toto"))
    fmt.Println(v.name) // Prints Toto instead of Thomas because of the pointer receiver which altered the name
}

Interfaces

They are implicitly implemented

type I interface {
    aMethod()
}

type person struct {
    name string
    age     int
}

func (p person) aMethod() {
    fmt.Println(p.name)
}

func main() {
    var p1 I = person{name: "Thomas", age: 28}
    p1.aMethod()
    fmt.Println(p1.name) // won't compile because person isn't accessible (p1 is an interface before all). Only aMethod (from the interface) is accessible
}

Type assertions

var i interface{} = "hello" // empty interface initialized with a string
s := i.(string)
fmt.Println(s) // displays hello as it's effectively a string

s, ok := i.(string) // here s = hello and ok = true
f, ok := i.(float64) // f = 0, ok = false

Error handling

i, err := strconv.Atoi("42") // Some action
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

Goroutines

It's basically a lightweight thread managed by the go runtime.

func main() {
    go foo() // foo is executed in another thread
    foo()       // foo is executed in the main thread
}

Channels

It's a mechanism provided to send / receive values thanks to its operator <-

ch <- v sends v value to channel ch

v := <- ch receive from ch and assigns value to v

Create one channel of int with make(chan int)

Passing channels as parameters

func ping(pings chan<- string, msg string) { // pings can be used to send values (msg)
    pings <- msg
}

func pong(pings <-chan string, pongs chan<- string) { // pings is used to receive values and pongs to send
    msg := <-pings
    pongs <- msg
}

Note the importance of <- positions!

Hint: By default, channels are blocking. It means the channel blocks the sender until the receiver is available and the receiver is blocked until a message is available.

To go through this behavior, one can use buffered channels:

make(chan int, 50)

It will then block after 50 values instead of one. Might be useful for assymmetric loading ;)

Select

A way to handle multiple channels. Use it inside a for-loop

select {
case v1 := <-c1:
    fmt.Printf("received %v from channel 1\n", v1)
case v2 := <-c2:
    fmt.Printf("received %v from channel 2\n", v2)
default:
    fmt.Printf("No one was ready for communication\n")
} 

Adding timer

select {
case v1 := <-c1:
    fmt.Println("do smth")
case <-time.After(1 * time.Second):
    fmt.Prinln("timeout after 1s")
}

Timers and tickers

Timers

Used to execute some code in the future once

timer1 := time.NewTimer(2 * time.Second)
<-timer1.C
fmt.Println("timer1 fired!")


timer2 := time.NewTimer(3 * time.Second)
go func() {
    <-timer2.C
    fmt.Println("timer2 fired!")
}()
stop2 := timer2.Stop()
if stop2 {
    fmt.Println("timer2 stopped!")
}

Hint: Timers can be stopped contrary to sleeps!

Tickers

Same as timers but for multiple times executions

ticker := time.NewTicker(500 * time.Millisecond)
// ...
go func() {
    for {
        select {
        case <-done:
            return
        case t := <-ticker.C:
            fmt.Println("Tick at", t)
        }
    }
}()
// ...

Rob Pike's "patterns"

Generator

It's a function that returns a channel

func foo(msg string) <- string {
    c := make(chan string)
    go func() {
        // ...
    }()
    return c
}

func main () {
    // ...
    f := foo("Thomas")
    g := foo("Toto")

    fmt.Println(<-f) // Here I can use the
    fmt.Println(<-g) // channel to display its values
    // ...
}

Multiplexer (fan-in)

It aggregates multiple channels into one

func fanIn(a, b <-chan string) <-chan string {
    c := make(chan string)
    go func() { for { c <- <-a } }()
    go func() { for { c <- <-b } }()
    return c
}

func main() {
    c:= fanIn(foo("Thomas"), foo("Toto"))
}

Or using select (with only one goroutine)

func fanIn(a, b <-chan string) <-chan string {
    c := make(chan string)
    go func() { 
        for {
            select {
            case s := <-a:
                c <- s
            case s := <-b:
                c <- s
            }
        }
    }()
    return c
}

WaitGroups

The idea here is to wait for a bunch of routines to complete before doing something else:

package main

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

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // Set the wait group to Done status after the worker is done

	fmt.Printf("Worker %d starting..\n", id)
	time.Sleep(time.Second) // faking process
	fmt.Printf("Worker %d finished!\n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i < 5; i++ {
		wg.Add(1)         // Adding 1 process to the wait group
		go worker(i, &wg) // launch goroutine with the associated wait group
	}

	wg.Wait()

	fmt.Println("All process are done!")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment