Go is a compiled language developed by Robert Griesemer, Rob Pike, and Ken Thompson at Google. It is primarily used in cloud-native and web development projects.
Some of the popular projects created with Go
- Docker: Container Engine
- Kubernetes: Container Orchestrator
- Hugo: Static Site Generator (Also used to generate this blog)
- Helm: Package manager for Kubernetes
Packages
A Package abstraction is created in Go to group functions that are related to each other or associated with a specific task. At the start of each source file (*.go
) a package name is assigned to it using the package
keyword, for example, package main
.
To import a package we use the import
keyword with the name of the package enclosed in ""
like import "fmt"
. The constants, functions, and variables imported from a package are referred to with their first letter capitalized like math.Pi
and math.Pow()
.
package main
import "fmt"
func main () {
return 0
}
Packages could be bundled together as modules. The go.mod
file declares the path to a Go module and all packages within it. A Go module is initialized using the go mod init
command followed by the name of the module
go mod init exampleModule
This will generate a go.mod
file in the directory specifying the name of the module (exampleModule
) and its dependencies.
Standard Input and Output
The fmt
package provides functions for standard input and output, these help return output from the program and take inputs from the user respectively. The fmt.Println()
function outputs its arguments to the terminal and fmt.Scanln()
takes input from the terminal and assigns them to variables. Variations of Println
and Scanln
provided by fmt
:
fmt.Print()
: Standard output without any newline at the end.fmt.Scan()
: Values are read from standard input and the newline is interpreted as space.fmt.Printf()
: Standard output in the specified format.fmt.Scanf()
: Reads the value from the terminal in the specified format.
package main
import "fmt"
func main () {
var name string
fmt.Print("Your name:")
fmt.Scan(&name)
fmt.Println("Hello", name)
}
// Output
// Your name: Joe
// Hello Joe
Building and Executing
To compile and build a Go package as a binary we use the go build
command.
go build hello.go
This will create a binary named hello
(or hello.exe
in Windows) in the current directory that could be executed directly by the host OS.
We can execute a Go package directly from the source file using the go run
command.
go run hello.go
Constants
const
keyword is used to define constants, values that don’t have to be changed throughout the program. Go supports numeric, character, string, and boolean constants.
package main
import "fmt"
func main () {
const pi = 3.14
fmt.Println("Approximate value of Pi is:", pi)
}
// Output
// Approximate value of Pi is: 3.14
Variables and Datatypes
To initialize a variable in Go we have to use the var
keyword followed by the name of the variable and its datatype like var intExample int
.
Go has the following datatypes
bool
for boolean values. Eithertrue
orfalse
.string
for storing single or multiple characters.rune
for storing the Unicode code point of a character.int
for integers. Go also supportsint8
,int16
,int32
, andint64
where the digits afterint
signifies the number of bits required to represent the value.uint
for unsigned integers (integers without + or -).uint8
/byte
,uint16
,uint32
,uint64
are also available in Go.- Variables of the
uintptr
type are used to store integer representations of memory addresses. They are useful for performing pointer arithmetic provided byunsafe.Pointer
. float32
andfloat64
for values that require decimal precision.complex64
andcomplex128
are used to represent 64 and 128-bit complex numbers like $5\ +\ i6$ (where $i$ represents $\sqrt{(-1)}$)
package main
import "fmt"
func main () {
var intExample int
intExample = 3
fmt.Println("Integer Example:", intExample)
var floatExample float64
floatExample = 2.3
fmt.Println("Float Example:", floatExample)
var stringExample string
stringExample = "Hello, World"
fmt.Println("String Example:", stringExample)
var boolExample bool
boolExample = true
fmt.Println("Boolean Example:", boolExample)
var complexExample complex64
complexExample = complex(5, 6)
fmt.Println("Complex Number Example:", complexExample)
}
// Output
// Integer Example: 3
// Float Example: 2.3
// String Example: Hello, World
// Boolean Example: true
// Complex Number Example: (5+6i)
To declare and assign values to a variable without explicitly specifying its datatype we use :=
operand, the variable’s datatype will be inferred during compile time by the value on the right side of the operand.
package main
import "fmt"
func main () {
intExample := 3
fmt.Println("Integer Example:", intExample)
floatExample := 2.3
fmt.Println("Float Example:", floatExample)
stringExample := "Hello, World"
fmt.Println("String Example:", stringExample)
boolExample := true
fmt.Println("Boolean Example:", boolExample)
complexExample := complex(5, 6)
fmt.Println("Complex Number Example:", complexExample)
}
// Output
// Integer Example: 3
// Float Example: 2.3
// String Example: Hello, World
// Boolean Example: true
// Complex Number Example: (5+6i)
If a variable is initialized without any value then a zero value will be assigned to it by default. Zero values for different datatypes are 0
(for int
), 0.0
(for float64
), ""
(for string
), and false
(for bool
).
package main
import "fmt"
func main () {
var example1 int
fmt.Println(example1)
var example2 float64
fmt.Println(example2)
var example3 string
fmt.Println(example3)
var example4 bool
fmt.Println(example4)
}
// Output
// 0
// 0
//
// false
Type Conversion
Datatypes could also be used as functions for type conversion of variables for example float64(intExample)
.
package main
import "fmt"
func main () {
intExample := 4
fmt.Printf("Float conversion of intExample: %0.2f", float64(intExample))
}
// Output
// Float conversion of intExample: 4.00
Functions
Functions allow us to aggregate frequently used instructions as a single statement. To define a function in Go we use the func
keyword followed by the name of the function, its arguments (inputs), and the datatype of the return values like func exampleFunc (inputValue int) (outputValue int) { }
package main
import "fmt"
func square (x int) (int) {
return x*x
}
func sumAndDifference (x int, y int) (int, int){
return x+y, x-y
}
func main () {
value1 := 5
fmt.Println("Square of", value1, "is:", square(value1))
value2 := 8
sum, difference := sumAndDifference(value1, value2)
fmt.Println("Sum of", value1, "and", value2, "is:", sum)
fmt.Println("Difference of", value2, "from", value1, "is:", difference)
}
// Output
// Square of 5 is: 25
// Sum of 5 and 8 is: 13
// Difference of 8 from 5 is: -3
Anonymous Functions
Functions are first-class citizens in Go i.e. they could be defined inside variables, passed as arguments, and returned as values from other functions.
Functions with no name (called Anonymous functions) could be created to perform a set of instructions once in a program.
package main
import "fmt"
func main () {
// Anonymous Function
func (x int) {
fmt.Println("Hello, this is not a random number:", x)
}(1)
// Function as Variable
funcExample := func (x int){
fmt.Println("Example Function")
fmt.Println("Input:", x)
}
funcExample(3)
}
// Output
// Hello, this is not a random number: 1
// Example Function
// Input: 3
Conditions
Go supports if
, else if
, and else
for defining conditional statements.&&
(AND) and ||
(OR) operators could be used to create complex conditional expressions.
package main
import "fmt"
func evaluateGrades (marks int) string {
if marks < 101{
if marks < 40{
return "D"
} else if marks >= 40{
return "C"
} else if marks >= 50{
return "B"
} else if (marks >= 60){
return "A"
}
}
return "INVALID"
}
func main () {
marks := 4
fmt.Println("Grade for marks:", marks, "is:", evaluateGrades(marks))
marks = 40
fmt.Println("Grade for marks:", marks, "is:", evaluateGrades(marks))
marks = 51
fmt.Println("Grade for marks:", marks, "is:", evaluateGrades(marks))
marks = 101
fmt.Println("Grade for marks:", marks, "is:", evaluateGrades(marks))
}
// Output
// Grade for marks: 4 is: D
// Grade for marks: 40 is: C
// Grade for marks: 51 is: C
// Grade for marks: 101 is: INVALID
Switch Cases
Sometimes a switch
could be relatively clean compared to multiple if
and else if
statements.
Unlike if-else
the first case
which satisfies the condition will be executed and the following cases will be ignored. If none of the case
conditions are satisfied then the default
statement is executed.
package main
import "fmt"
func evaluate (value1, value2, operation int) (int) {
switch{
case operation==1:
return value1+value2
case operation==2:
return value1-value2
case operation==3:
return value1*value2
case operation==4:
return value1/value2
default:
return (-1)
}
}
func main () {
var value1, value2, operation int
fmt.Println("Calculator Program:")
fmt.Print("Enter value1:")
fmt.Scanln(&value1)
fmt.Print("Enter value2:")
fmt.Scanln(&value2)
fmt.Println("\nSelect Operation to Perform")
fmt.Println("1) ADD")
fmt.Println("2) SUBTRACT")
fmt.Println("3) MULTIPLY")
fmt.Println("4) DIVIDE")
fmt.Print("Enter the operation number:")
fmt.Scanln(&operation)
fmt.Println("Result of Operation is:", evaluate(value1, value2, operation))
}
// Output
// Calculator Program:
// Enter value1:56
// Enter value2:54
// Select Operation to Perform
// 1) ADD
// 2) SUBTRACT
// 3) MULTIPLY
// 4) DIVIDE
// Enter the operation number:3
// Result of Operation is: 3024
Additional Datatypes
Arrays
We create arrays to store a sequence of values with the same datatype. Go arrays have explicitly defined sizes associated with their datatype.
Individual elements in an array could be accessed by their index (starting from 0
). For example, to access the 3rd element in the array
we can simply call array[2]
.
An array is defined as array := [5]int{2, 4, 8, 16, 32}
. len()
function returns the length of the array.
package main
import "fmt"
func main() {
var arrayExample [5]int
fmt.Println("Zero Value Array:", arrayExample)
arrayExample[1]=2
fmt.Println("Changed arrayExample:", arrayExample)
arrayExample2 := [5]int{2, 3, 1, 3, 21}
fmt.Println("Another arrayExample:", arrayExample2)
}
// Output
// Zero Value Array: [0 0 0 0 0]
// Changed arrayExample: [0 2 0 0 0]
// Another arrayExample: [2 3 1 3 21]
Slices
A segment of an array could be extracted by creating its slice. To extract a segment starting from index 2
till index 5
in an array called arrayExample
we can simply write arrayExample[2:5]
package main
import "fmt"
func main() {
arrayExample2 := [7]int{2, 3, 1, 3, 21, 234, 23}
lowIndex := 2
highIndex := 5
fmt.Println("Slice of arrayExample starting from index",
lowIndex, "to", highIndex, "is:",
arrayExample2[lowIndex:highIndex])
fmt.Println("Slice of arrayExample starting from index",
lowIndex, "till (including)", highIndex, "is:",
arrayExample2[lowIndex:highIndex+1])
fmt.Println("Slice Length:", len(arrayExample2[lowIndex:highIndex+1]))
fmt.Println("Capacity of the original array:",
cap(arrayExample2[lowIndex:highIndex+1]))
}
// Output
// Slice of arrayExample starting from index 2 to 5 is: [1 3 21]
// Slice of arrayExample starting from index 2 till (including) 5 is: [1 3 21 234]
// Slice Length: 4
// Capacity of the original array: 5
Any changes performed on the slice will also affect the original array.
The cap()
function is used to return the capacity of the original array.
The length of the slice is not part of its datatype, so slices are also used as dynamically-sized arrays. A slice is created just like an array but without any size in its declaration like var sliceExample []int
package main
import "fmt"
func main() {
var capitals []string
capitals = append(capitals, "delhi")
capitals = append(capitals, "london")
fmt.Println("Slice of Capitals:", capitals)
fmt.Println("Length of Capitals Slice:", len(capitals))
fmt.Println("Capacity of Capitals Slice:", cap(capitals))
}
// Output
// Slice of Capitals: [delhi london]
// Length of Capitals Slice: 2
// Capacity of Capitals Slice: 2
Maps
To store key-value pairs in Go we have Maps. We can define a map that stores string
keys mapped to bool
values with the help of make()
function like mapExample := make(map[string]bool)
.
package main
import "fmt"
func main() {
mapExample := make(map[string]bool)
mapExample["yes"] = true
mapExample["no"] = false
fmt.Println("Example of Map:", mapExample)
}
// Output
// Example of Map: map[no:false yes:true]
Structs
Go is a procedural language but to create data structures with multiple fields we have struct
. By creating a struct
we are adding a new datatype to our program, that’s why the type
keyword is used in its declaration.
While creating an instance of the struct
we can pass the value to its fields like maps where the key is the name of the field.
package main
import "fmt"
type dog struct {
name string
age int
}
func main() {
dog1 := dog{name: "DoggyMcDogFace", age: 1}
fmt.Println("Dog1:", dog1)
dog2 := dog{name: "AnotherDog", age: 3}
fmt.Println("Dog2:", dog2)
}
// Output
// Dog1: {DoggyMcDogFace 1}
// Dog2: {AnotherDog 3}
Methods
Methods refer to the functions created to be executed on a specific datatype (like a struct
). Their declaration is similar to functions with the addition of struct
argument after func
keyword like func (d dog) showFields() () { }
package main
import "fmt"
type dog struct {
name string
age int
}
func (d dog) showFields() (){
fmt.Println("Name of Dog:", d.name)
fmt.Println("Age of Dog:", d.age)
}
func main() {
dog1 := dog{name: "DoggyMcDogFace", age: 1}
dog1.showFields()
dog2 := dog{name: "AnotherDog", age: 3}
dog2.showFields()
}
// Output
// Name of Dog: DoggyMcDogFace
// Age of Dog: 1
// Name of Dog: AnotherDog
// Age of Dog: 3
Interfaces
Interfaces are used to group common methods.
Interfaces enable polymorphism in Go, which means a method’s behavior is determined by the datatype it is applied upon.
package main
import "fmt"
type ubuntu struct {
releaseName string
releaseVersion float64
}
func (u ubuntu) showUpdateCommand() {
fmt.Println("Update command for", u.releaseName, "is")
fmt.Println("\tapt-get update && apt-get upgrade")
}
type fedora struct {
releaseName string
releaseVersion float64
}
func (f fedora) showUpdateCommand() {
fmt.Println("Update command for", f.releaseName, "is")
fmt.Println("\tdnf update")
}
type distro interface {
showUpdateCommand()
}
func main() {
var exampleDistro, exampleDistro2 distro
exampleDistro = ubuntu{releaseName: "Lunar Lobster", releaseVersion: 23.04}
fmt.Println("Example of a distro:", exampleDistro)
exampleDistro.showUpdateCommand()
exampleDistro2 = fedora{releaseName: "Fedora 39", releaseVersion: 39.00}
fmt.Println("Example of another distro:", exampleDistro2)
exampleDistro2.showUpdateCommand()
}
// Output
// Example of a distro: {Lunar Lobster 23.04}
// Update command for Lunar Lobster is
// apt-get update && apt-get upgrade
// Example of another distro: {Fedora 39 39}
// Update command for Fedora 39 is
// dnf update
For Loops
Loops allow us to execute a statement multiple times until some condition is valid.
A for
loop requires three statements:
- initializing statement: This statement will be executed once before the loop has started.
- conditional expression: This expression will be evaluated after each execution of the loop. The loop will stop if this expression evaluates to
false
. - incrementing statement: This statement is executed after each iteration of the loop
package main
import "fmt"
func main() {
// Initializing Statement: i:=0 (i is initialized with value 0)
// Conditional Expression: i<10 (until i is less than 10)
// Incrementing Statement: i++ (increment i by 1)
for i:=0; i<10; i++ {
fmt.Println("Value of i:", i)
}
}
// Output
// Value of i: 0
// Value of i: 1
// Value of i: 2
// Value of i: 3
// Value of i: 4
// Value of i: 5
// Value of i: 6
// Value of i: 7
// Value of i: 8
// Value of i: 9
For Loop over an Array
len(array)
will return the length of an array, by using its length we can calculate the last index of an array i.e. len(array)-1
. This will help us write a loop that accesses each element of an array using its index.
package main
import "fmt"
func main() {
arrayExample := [6]int{123, 21, 3, 54, 65, 45}
for i:=0; i<len(arrayExample); i++ {
fmt.Println("Element at index ", i, "in arrayExample is", arrayExample[i])
}
}
// Output
// Element at index 0 in arrayExample is 123
// Element at index 1 in arrayExample is 21
// Element at index 2 in arrayExample is 3
// Element at index 3 in arrayExample is 54
// Element at index 4 in arrayExample is 65
// Element at index 5 in arrayExample is 45
Range
To iterate over data structures such as slices, maps, and strings we can take the help of range
. Each iteration of a loop implemented with range
will return two values depending on the data structure passed to it:
- for string, it will return the byte position of the character and character
- for array/slices, it will return the index and the element
- for maps, it will return the key and value
package main
import "fmt"
func main() {
var sliceExample []float64
sliceExample = append(sliceExample, 4.3)
sliceExample = append(sliceExample, 5.2)
sliceExample = append(sliceExample, 64.3)
sliceExample = append(sliceExample, 34.8)
fmt.Println("Example of a Slice:", sliceExample)
for index, value := range sliceExample {
fmt.Println("Value at index", index, "in sliceExample is", value)
}
stringExample := "Hello, World"
fmt.Println("Example of a String:", stringExample)
for pos, char := range stringExample {
fmt.Printf("%#U character is at position %d\n", char, pos)
}
mapExample := make(map[int]string)
mapExample[1] = "one"
mapExample[2] = "two"
mapExample[4] = "four"
fmt.Println("Example of a Map:", mapExample)
for key, value := range mapExample {
fmt.Println("Value for key", key, "in mapExample is", value)
}
}
// Output
// Example of a Slice: [4.3 5.2 64.3 34.8]
// Value at index 0 in sliceExample is 4.3
// Value at index 1 in sliceExample is 5.2
// Value at index 2 in sliceExample is 64.3
// Value at index 3 in sliceExample is 34.8
// Example of a String: Hello, World
// U+0048 'H' character is at position 0
// U+0065 'e' character is at position 1
// U+006C 'l' character is at position 2
// U+006C 'l' character is at position 3
// U+006F 'o' character is at position 4
// U+002C ',' character is at position 5
// U+0020 ' ' character is at position 6
// U+0057 'W' character is at position 7
// U+006F 'o' character is at position 8
// U+0072 'r' character is at position 9
// U+006C 'l' character is at position 10
// U+0064 'd' character is at position 11
// Example of a Map: map[1:one 2:two 4:four]
// Value for key 1 in mapExample is one
// Value for key 2 in mapExample is two
// Value for key 4 in mapExample is four
Substitute for while
Loop
Go doesn’t have while
loops but we can use for
loops for the same purpose by executing it with just the conditional expression.
package main
import "fmt"
func main() {
fmt.Println("Enter an odd number to end the loop")
flag := false
for flag==false {
var input int
fmt.Printf("Enter a value:")
fmt.Scanln(&input)
if input%2!=0{
flag=true
}
}
fmt.Println("Loop ended")
}
// Output
// Enter an odd number to end the loop
// Enter a value:2
// Enter a value:42
// Enter a value:52
// Enter a value:68
// Enter a value:69
// Loop ended
Pointers
Pointers for a datatype will store the address of a value rather than the value itself. So an integer pointer (*int
) will store the address of an integer value.
To fetch the address of a value we use &
operand. For example, to fetch the address of the value stored in the intExample
variable we call &intExample
. Similarly to fetch the value stored at an address we use *
operand like *(&intExample)
.
package main
import "fmt"
func main() {
intExample := 2
fmt.Println("Value of intExample:", intExample)
fmt.Println("Memory address of intExample:", &intExample)
var pointerExample *int
pointerExample = &intExample
fmt.Println("Value of pointerExample:", pointerExample)
fmt.Println("Value stored at pointerExample address:", *pointerExample)
}
// Output
// Value of intExample: 2
// Memory address of intExample: 0xc000022050
// Value of pointerExample: 0xc000022050
// Value stored at pointerExample address: 2
When methods are defined on the pointer of the struct
rather than the struct
themselves then they can manipulate the value of fields in the original instance rather than its copy.
package main
import "fmt"
type car struct {
color string
}
func (c car) changeColor(color string) {
c.color = color
}
func (c *car) changeColorPointer(color string) {
c.color = color
}
func main() {
newCar := car{color: "red"}
fmt.Println("newCar: ", newCar)
// This will not work as the change is not performed
// on the original memory address but on its copy
newCar.changeColor("blue")
fmt.Println("newCar after changing color: ", newCar)
var newCarPtr *car
newCarPtr = &newCar
newCarPtr.changeColorPointer("blue")
fmt.Println("newCar after changing color using pointer: ", newCar)
}
// Output
// newCar: {red}
// newCar after changing color: {red}
// newCar after changing color using pointer: {blue}
Errors
In Go, errors are like other return types. So a function can return two values one being the output of the function and the other value could be an error
interface. We can declare this in the function’s signature like func sampleFunc(value int) (int, error) { }
.
A custom error is created using errors.New()
function is available in the errors
package.
package main
import (
"fmt"
"errors"
)
func divide(value1, value2 int) (int, error) {
if value2==0 {
return -1, errors.New("Divide by Zero Error. Change value2")
}
return value1/value2, nil
}
func main() {
var value1, value2 int
fmt.Printf("Enter value1:")
fmt.Scanln(&value1)
fmt.Printf("Enter value2:")
fmt.Scanln(&value2)
quotient, err := divide(value1, value2)
fmt.Println("Quotient:", quotient)
if err!= nil {
fmt.Println("Error:", err)
}
}
// Output
// Enter value1:1
// Enter value2:0
// ERROR!
// Quotient: -1
// Error: Divide by Zero Error. Change value2
To catch an unexpected error and exit the program we call the panic()
function with a string or the error as its argument. If we don’t want the program to abort on panic()
we can follow it up with a recover()
function.
Thank you for taking the time to read this blog post! If you found this content valuable and would like to stay updated with my latest posts consider subscribing to my RSS Feed.