Skip to content

Instantly share code, notes, and snippets.

@jpukg
Forked from tysonpaul89/go_cheatsheet.md
Created February 14, 2022 21:00
Show Gist options
  • Save jpukg/d6d8139fd0f2e3a19f8f6baeeb389aea to your computer and use it in GitHub Desktop.
Save jpukg/d6d8139fd0f2e3a19f8f6baeeb389aea to your computer and use it in GitHub Desktop.
Go Cheatsheet

Go

Go 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.

This cheat sheet is prepared by refering the book Introducing Go Build Reliable, Scalable Programs by Caleb Doxsey

Topics

Single and Multi-line comments

// This is a single line comment in Go lang.

/*This is a multi-line comment in Go lang.
Contents within the comment blocks will be ignored.*/

Basic Data Types

Intergers

Type Range
uint8 0 to 255
uint16 0 to 65535
uint32 0 to 4294967295
uint64 0 to 18446744073709551615
int8 -128 to 127
int16 -32768 to 32767
int32 -2147483648 to 2147483647
int64 -9223372036854775808 to 9223372036854775807
byte Alias for uint8
rune Alias for int32
int Machine dependent type. Size of this type depends on the system architecture.
uint Machine dependent type. Size of this type depends on the system architecture.
uintptr Machine dependent type. Size of this type depends on the system architecture.

Floating-Points

Type Range
float32 IEEE-754 32-bit floating-point numbers
float64 IEEE-754 64-bit floating-point numbers

Variables

Variable name syntax

// Varibale Names must start with a letter and 
// may contain letters, numbers, or the underscore symbol(_).
// Go uses camelCase variable names.
firstName := "Joe"
var lastName := "Jill"

Variable assignment

var x string = "Hello World!"

// No need to mention type. Go compiler will identify it based on the value.
x := "Hello World!"
var y = x

// Constants are varaibles whose value cannot be changed later.
const x string = "Hello World!"

String concatenation

x := "Hello "
x += "World!"
y := "Hello " + "World!"

Variable Scope

// A variable exists within the nearest curly braces ({}),
// or block, including any nested curly braces (blocks),
// but not outside of them.

package main
import "fmt"

var y int32 = 10

func main() {
	x := 5
	fmt.Println(y)
	fmt.Println(x)
	scopeExample()
}

func scopeExample() {
	fmt.Println(y)
	fmt.Println(x) // This will cause an error bcoz x doesn't exits in this function's scope.
}

Defining multiple varaibles

var (
	a = 1
	b = "Test"
)

fmt.Println(a) // 1
fmt.Println(b) // Test

Complex Data types

Arrays

An array is a numbered sequence of elements of a single type with a fixed length.

// Defining array
var x[5]int

// Array value assignment
x[0]=10
x[1]=20
x[4]=30
fmt.Println(x) // [10 20 0 0 30]

// Shorter syntax for creating arrays:
x := [5]float64{ 98, 93, 77, 82, 83 }
fmt.Println(x) // [98 93 77 82 83]

Slices

A slice is a segment of an array. Like arrays, slices are indexable and have a length. Unlike arrays, length of a slice is allowed to change.

x := make([]float64, 5) // This will make a slice of length 5
x[0] =  10
x[4] =  20
fmt.Println(x) // [10 0 0 0 20]

Slices are always associated with some array, and although they can never be longer than the array, they can be smaller.

x := make([]float64, 3, 5) // This will make of slice of length 3 from array of length 5

x := []int{1,2,3} // This is also valid

// Another way of creating slices is by using the [low : high] expression
x  := [5]int8{3, 6, 9, 12, 15}
y  := x[0:3]
fmt.Println(x) // [3 6 9]
// Here, low is the index of where to start the slice and high is the index of where to end it.

// Like python we can do this also
fmt.Println(x[:3]) // [3 6 9]
fmt.Println(x[2:5]) // [9 12 15]

append() function is used to add element to slice. While appeding if there’s not sufficient capacity in the underlying array, then a new array is created and all of the existing elements are copied over new one.

x := []int{1,2,3}
y : append(x, 4, 5)
fmt.Println(x) // [1 2 3]
fmt.Println(y) // [1 2 3 4 5]

copy() function

x  := []int{1, 2, 3} // length = 3
y  :=  make([]int, 2) // lenfth = 2
copy(y, x) // Copies elements in slice x to slice y
fmt.Println(x) // [1 2 3]
fmt.Println(y) // [1 2] Since length of y is 2, only 2 elements from x is copied.

Maps

Maps is an unordered collection of key-value pairs. They are also called assocative array, hash tables or dictionaries in other languages.

// Here keys are of type string and values will be int
// Also maps have to intialized before they can be used.
x := make(map[string]int) 
x["one"] = 1
x["two"] = 2
fmt.Println(x) // map[one: 1 two:2]

To delete items from a map, uses the built-in delete function:

delete(x, "one")
fmt.Println(x) // map[two:2]

Accessing elements not defined in the map will return empty/zero value.

x  :=  map[string]int{ // Another way of creating maps
	"one": 1,
	"two": 2,
}

y  :=  map[string]string{
	"one": "Uno",
	"two": "dos",
}

fmt.Println(x["three"]) // 0
fmt.Println(y["three"]) // ""

// Better way to check map value
value, ok := x["three"]
if ok {
	fmt.Println(value)
}

// OR

if value, ok := x["three"]; ok {
	fmt.Println(value)
}

Complex map example:

product := map[int]map[string]string{
	101: map[string]string{
		"name":     "Iphone 12",
		"category": "Mobile",
	},
	102: map[string]string{
		"name":     "Mac Book Air",
		"category": "Laptop",
	},
}

fmt.Println(product[102]["name"]) // Mac Book Air

if element, ok := product[101]; ok {
	fmt.Println(element["name"]) // Iphone
}

Struct

Struct is a data type that contains named fields and methods.

package main

import "fmt"

type Animal struct { // <-- Struct definition
	name      string // <-- Stuct field
	legs, age int8 
}

func (a *Animal) sound() string { // <-- Struct method definition
	if a.name == "Cat" {
		return "Meow"
	} else if a.name == "Tiger" {
		return "Roar"
	} else {
		return "bla bla bla"
	}
}

func main() {
	// Struct initialization method 1
	// Initializes struct with given name and legs value. For age a zero value will be assigned.
	cat := Animal{name: "Cat", legs: 4}
	cat.age = 2 // <-- Accessing struct fields

	// Struct initialization method 2
	tiger := Animal{"Tiger", 4, 5}

	// Struct initialization method 3
	// This will allocate memory for the fields, set zero value and returns a pointer of the struct.
	unknown := new(Animal)
	unknown.name = "unknown"

	describe(cat)
	// I am a Cat I have 4 legs, I am 2 years old and I make Meow sound.

	describe(tiger)
	// I am a Tiger I have 4 legs, I am 5 years old and I make Roar sound.

	describe(*unknown) // Since unknown is a pointer we have prefix *
	// I am a unknown I have 0 legs, I am 0 years old and I make bla bla bla sound.
}

func describe(animal Animal) {
	fmt.Println(
		"I am a", animal.name, "I have", animal.legs,
		"legs, I am", animal.age, "years old and",
		"I make", animal.sound(), "sound.",
	)
}

A struct’s fields usually represent the has-a relationship (e.g., a Circle has a radius ).

package main

import "fmt"

type Person struct {
	name string
}

func (p *Person) talk() {
	fmt.Println("Hi, my name is", p.name)
}

type Android struct {
	Person // <-- Person is a field of Android
	model  string
}

func main() {
	// Android{model: "2020", name: "Terminator"} will get an error.
	a := Android{model: "2020"}
	a.name = "Terminator" // <-- We can access Person.name directly
	a.talk()              // Hi, my name is Terminator

	b := new(Android)
	b.name = "Wall-E"
	b.model = "2021"
	b.talk() // Hi, my name is Wall-E
}

interface

Interfaces is a custom type that contains method or methods. you are not allowed to create an instance of the interface. But you are allowed to create a variable of an interface type and this variable can be assigned with a concrete type value that has the methods the interface requires.

package main

import (
	"fmt"
	"math"
)

type Geometry interface {
	area() float64
}

type Rectangle struct {
	width, height float64
}

type Circle struct {
	radius float64
}

func (r Rectangle) area() float64 { // <-- Implementing area() method
	return r.width * r.height
}

func (c Circle) area() float64 {
	return 2 * math.Pi * c.radius
}

func measure(g Geometry) { // <-- Interface type is used.
	fmt.Println(g)
	fmt.Println(g.area())
}

func main() {
	r := Rectangle{width: 3, height: 4}
	c := Circle{radius: 5}

	measure(r)
	// {3 4}
	// 12

	measure(c)
	// {5}
	// 31.41592653589793
}

Control Structures

for Loop

Go has only one loop, which can be used in differnt ways.

Normal looping

for i := 0; i <= 10; i++ {
	fmt.Println(i)
}
// Output:
// 0
// 1
// ...
// 10

As while loop

i := 0

for i <= 10 {
	fmt.Println(i)
	i++
}

// Output:
// 0
// 1
// ...
// 10

Looping an iterator

arr := [3]int8{10, 20, 30}

for  i, val  :=  range arr {
	fmt.Println(i, val)
}

// Output:
// 0 10
// 1 20
// 2 30

// If you don't want to the print loop index, then use underscore(_) to avoid unsed variable error.
for  _, val  :=  range arr {
	fmt.Println(val)
}

Functions

Normal function

package main

import "fmt"

func main() {
	c := total(2, 5)
	fmt.Println(c)
}

func total(a int, b int) int {
	return a + b
}

Functions with named return type

package main

import "fmt"

func main() {
	c := total(2, 5)
	fmt.Println(c)
}

func total(a int, b int) (sum int) {
	sum = a + b
	return
}

Multiple return values

package main

import "fmt"

func main() {
	res, isEvent := total(2, 5)
	fmt.Println(res) // 7
	fmt.Println(isEvent) // false
}

func total(a int, b int) (int, bool) {
	sum := a + b
	isEven := sum%2 == 0
	return sum, isEven
}

Variadic Functions

Function that can accept any number of parameters. Its similar to the *args in the python

package main

import "fmt"

func main() {
	res := total(2, 5, 3)
	fmt.Println(res) // 10
}

func total(args ...int) int {
	sum := 0
	for _, num := range args {
		sum += num
	}

	return sum
}

Closure

A function inside a function with access to a non-local variable is known as a closeure

package main

import "fmt"

func main() {
	x := 0
	increment := func() int { // This is a Closure
		x++ // This is non-local variable
		return x
	}

	fmt.Println(increment()) // 1
	fmt.Println(increment()) // 2
}

Recursion

Function that call itself during execution is called recursion

package main

import "fmt"

func main() {
	fmt.Println(factorial(3)) // 6
}

func factorial(x uint) uint {
	if x == 0 {
		return 1
	}
	return x * factorial(x-1)
}

Defer statement

Function with defer statement schedules a calls to run after the function completes. Its is usually used for performing clean up actions.

package main

import "fmt"

func main() {
	defer first()
	second()
	// Output:
	// Second
	// First
}

func first() {
	fmt.Println("First")
}

func second() {
	fmt.Println("Second")
}

panic and recover

panic and recover is simlar to try..catch in other languages. panic is used to throw and error and recover is used catch the error. In Go the recover function should be called inside a defer statement. Also the recover function should be called inside the function were is panic is callled.

package main

import "fmt"

func main() {
	div1 := division(1, 2)
	fmt.Println(div1) // 0.5

	div2 := division(3, 0) // Denominator Can't be zero.
	fmt.Println(div2) // 0
}

func division(numerator float64, denominator float64) float64 {
	defer handleError()

	if denominator == 0 {
		panic("Denominator Can't be zero.")
	}

	return numerator / denominator
}

func handleError() {
	err := recover()
	if err != nil {
		fmt.Println(err)
	}
}

Pointers

A pointer holds the memory address of a value.

package main

import "fmt"

func main() {
	x := 10
	fmt.Println("x =", x) // x = 10
	resetValue(&x) // & is must be used when passing pointer.
	fmt.Println("After reset x =", x) // After reset x = 0
}

func resetValue(val *int) { // The type of the pointer varaibile mmust have a * prefix.
	fmt.Println("val =", val) // val = 0xc000126010
	// To asign value to a pointer * must be prefixed with the variable name.
	fmt.Println("*val =", *val) // *val = 10
	*val = 0
}

We can also get a pointer using a built-in new() function.

package main

import "fmt"

func main() {
	x := new(int) // Gets the pointer
	fmt.Println("x =", x)   // x = 0xc0000b8010
	fmt.Println("*x =", *x) // *x = 0
	setValue(x)
	fmt.Println("*x =", *x) // *x = 20
}

func setValue(z *int) {
	*z = 20
}

Packages

Do the following to create and run a package.

  • Create my_example directory in ~/go/src/ path
  • Create a directory named util in ~/go/src/my_example/ path
  • Create util.go inside the util directory and add the following code in it.
package util

func IsEven(num int) bool {
	if num%2 == 0 {
		return true
	} else {
		return false
	}
}
  • Go back to my_example directory, create main.go and add the following code.
package main

import (
	"fmt"
	"my_examples/util"
)

func main() {
	res := util.IsEven(4)
	fmt.Println(res) // true
}
  • Now run go run main.go

Thinks to remember when writing a package.

  • In a package, only the function name with upper camel casing is accessible from outside. Example: Average()
  • Functionan name with lower camel casing are private functions. Example: total()

Common Package Examples

strings operations

strings package is used to perform operations on strings.

package main

import (
	"fmt"
	"strings"
)

func main() {
	// Change to lower case.
	fmt.Println(strings.ToLower("Hello World!")) // hello world!

	// Change to upper case.
	fmt.Println(strings.ToUpper("Hello World!")) // HELLO WORLD!

	// String check if given sub-string present in a string.
	fmt.Println(strings.Contains("Hello World!", "World")) // true

	// To join list of strings into a single string.
	numbers := []string{"one", "two", "three"}
	fmt.Println(strings.Join(numbers, ", ")) // One, Two, Three

	// To split strings into a list of strings.
	numString := "uno, dos, tres"
	fmt.Println(strings.Split(numString, ", ")) // [uno dos tres]

	// String replace
	str := "one twenty one"
	fmt.Println(strings.Replace(str, "one", "three", 1))  // three twenty one
	fmt.Println(strings.Replace(str, "one", "three", 2))  // three twenty three
	fmt.Println(strings.Replace(str, "one", "three", -1)) // three twenty three
	fmt.Println(strings.ReplaceAll(str, "one", "three"))  // three twenty three
}

File read

io/ioutil package is use for file read.

package main

import (
	"fmt"
	"io/ioutil"
)

func main() {
	f, err := ioutil.ReadFile("hello.txt")
	if err != nil {
		return
	}

	fmt.Println(f) // [72 101 108 108 111 32 87 111 114 108 100 33]
	str := string(f)
	fmt.Println(str) // Hello World!
}

File write

os package is used for file write.

package main

import (
	"os"
)

func main() {
	f, err := os.Create("text.txt")
	if err != nil {
		return
	}

	defer f.Close()

	f.WriteString("Testing 124")
}

Goroutines

A goroutine is a function that is capable of running concurrently with other func‐ tions.

In order to run a function as a goroutine we have to use the go keyword. In the below example, if we run the code without go keyword, then we will get the following synchronous output.

2 is an even number.
4 is an even number.
6 is an even number.
Even numbers printed.
1 is an odd number.
3 is an odd number.
5 is an odd number.
Odd numbers printed.

When we run the below example code as goroutines, we will get the following asynchronous output. This proves that the functions were running concurrently.

Even numbers printed.
Odd numbers printed.
2 is an even number.
1 is an odd number.
4 is an even number.
3 is an odd number.
5 is an odd number.
6 is an even number.
package main

import (
	"fmt"
	"time"
)

func main() {
	arr := []int{1, 2, 3, 4, 5, 6}

	go printEvenNumbers(arr) // <-- Calling function as a goroutine
	fmt.Println("Even numbers printed.")

	go printOddNumbers(arr) // <-- Calling function as a goroutine
	fmt.Println("Odd numbers printed.")

	var someInput string
	fmt.Scan(&someInput) // Scan is added to prevent script from exiting.
}

func printEvenNumbers(nums []int) {
	for _, num := range nums {
		if num%2 == 0 {
			fmt.Println(num, "is an even number.")
			time.Sleep(250 * time.Millisecond) // Sleeps for 0.25 sec
		}
	}
}

func printOddNumbers(nums []int) {
	for _, num := range nums {
		if num%2 != 0 {
			fmt.Println(num, "is an odd number.")
			time.Sleep(250 * time.Millisecond) // Sleeps for 0.25 sec
		}
	}
}

Channels

Channels provide a way for two goroutines to communicate with each other and synchronize their execution.

package main

import (
	"fmt"
)

func main() {
	var result chan float64 = make(chan float64)
	// result := make(chan float64) <-- this is also valid

	arr1 := []float64{1, 2, 3, 4, 5, 6}
	arr2 := []float64{10, 20, 30, 40, 50, 60}
	go findAverage(float64(len(arr2)), result)
	go findTotal(arr1, result)
	go findAverage(float64(len(arr1)), result)
	go printResult(result)
	go findTotal(arr2, result)
	go printResult(result)

	var someInput string
	fmt.Scan(&someInput) // Scan is added to prevent script from exiting.
}

func findTotal(nums []float64, sum chan<- float64) { // <-- Here sum can only used for send message to channel.
	total := 0.0
	for _, num := range nums {
		total += num
	}
	fmt.Println("Sum =", total)
	sum <- total // <-- Sending message to chanel
}

func findAverage(length float64, result chan float64) { // <-- Here result is bidirectional. ie, it can send and receive message to/from channel.
	total := <-result // <-- Receiving message from chanel
	avg := total / length
	fmt.Println("Average calculated for", total, "/", length)
	result <- avg
}

func printResult(avg <-chan float64) { // <-- Here avg can only used for receiving message from channel.
	fmt.Println("Average =", <-avg)
}

Output of the above example:

Sum = 21
Average calculated for 21 / 6
Average = 3.5
Sum = 210
Average calculated for 210 / 6
Average = 35
  • In the above code, we called the findAverage(float64(len(arr2)), result) first but this function was executed after findTotal(arr2, result) because it was waiting for the message from channel.
  • Here code executoin was synchronous because the execution order was findTotal, findAverage and printResult.
  • We can use the channel direction on the channel type to restrict channel operations. In the above code, we use channel direction in findTotal function to allow only sending messages to channel and in the printResult function to allow only receiving messages from channel.
  • The findAverage is bidirectional because it can send and receive message from channel.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment