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
- Single and Multi-line comments
- Basic Data Types
- Variables
- Complex Data types
- Control Structures
- Defer statement
- panic and recover
- Pointers
- Packages
- Common Package Examples
- Goroutines
// 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.*/
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. |
Type | Range |
---|---|
float32 | IEEE-754 32-bit floating-point numbers |
float64 | IEEE-754 64-bit floating-point numbers |
// 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"
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!"
x := "Hello "
x += "World!"
y := "Hello " + "World!"
// 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.
}
var (
a = 1
b = "Test"
)
fmt.Println(a) // 1
fmt.Println(b) // Test
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]
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 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 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
}
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
}
Go has only one loop, which can be used in differnt ways.
for i := 0; i <= 10; i++ {
fmt.Println(i)
}
// Output:
// 0
// 1
// ...
// 10
i := 0
for i <= 10 {
fmt.Println(i)
i++
}
// Output:
// 0
// 1
// ...
// 10
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)
}
package main
import "fmt"
func main() {
c := total(2, 5)
fmt.Println(c)
}
func total(a int, b int) int {
return a + b
}
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
}
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
}
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
}
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
}
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)
}
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 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)
}
}
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
}
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
- 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()
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
}
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!
}
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")
}
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 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.