Skip to main content

Golang Introduction

Golang (or Go) is statically typed, compiled programming language designed at Google. It is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency.

In Golang, everything is a package. A package is a collection of source files in the same directory that are compiled together. A package can be imported by other packages. main is a special package that defines a standalone executable program, not a library.

Installation

  • Download and install Go

  • Garbage collected

  • Multithreading

  • concurrency

Packages

Module is a collection of related Go packages that are versioned together as a single unit.

  • fmt - formatted I/O with functions analogous to C's printf and scanf.
  • os - provides a platform-independent interface to operating system functionality.
  • strconv - implements conversions to and from string representations of basic data types.
  • time - provides functionality for measuring and displaying time.
  • math - provides basic constants and mathematical functions.
  • net/http - provides HTTP client and server implementations.
  • encoding/json - implements encoding and decoding of JSON.

Hello World - Running a program

package main // package declaration

import "fmt" // import fmt package

import ( "fmt" "os") // import multiple packages

func main() {
fmt.Println("Hello, World!")
}

We can run and compile the program using the following command:

$ go run hello.go

Go Mod (go.mod)

Go modules are a dependency management system that makes dependency version information explicit and easier to manage. Go modules are the future of dependency management in Go.

go mod init github.com/username/repo - creates a new module, initializing the go.mod file that describes it.

  • go mod tidy - command will add any missing modules necessary to build the current module's packages and dependencies. It will also remove any unused modules that don't provide any relevant packages. It will update the go.mod file and the go.sum file.

  • go mod verify - command will verify dependencies have expected content.

  • go list -m all - command will list all modules needed to build the current module, as well as indirect and test dependencies.

  • go get - command will add dependencies to current module and install them.

  • go list -m -versions <module name> - command will list all available versions of a module.

  • go mod edit -go 1.16 - command will update the go directive in the go.mod file to the specified version.

  • go mod vendor - command will copy all dependencies into a vendor directory.

Sum (go.sum)

It is a file that contains the expected cryptographic checksums of the content of specific module versions. It ensures that dependencies have not been modified.

Go Path

Go path is an environment variable that specifies the location of your workspace. It is used to find the location of your Go code.

Build

We can build a program using the following command:

$ go build hello.go

we can also build for different platforms using the following command:

$ GOOS=linux GOARCH=amd64 go build hello.go

Memory Allocation

  • new() - It allocates memory. The size of the memory is equal to the size of the type. It returns a pointer to the memory.
  • make() - It creates slices, maps, and channels only. It returns an initialized (not zeroed) value of type. It is used to create dynamically sized objects.

Garbage collection is a form of automatic memory management. The garbage collector frees the memory occupied by objects that are no longer in use by the program.

Print

There are serveral ways to print in Go. We can use fmt package to print.

  • fmt.Println - prints a line
  • fmt.Print - prints without a new line
  • fmt.Printf - prints with formatting

Placeholders for printf

  • %v - value in default format
  • %+v - value in default format with field names
  • %T - type of value
  • %t - boolean
  • %d - decimal integer
  • %b - binary integer
  • %c - character
  • %x - hexadecimal integer
  • %f - floating point number
  • %s - string

Escape sequences:

  • \n - newline
  • \t - tab

Variables and Constants

  • var - variables with initializers.
  • const - declares a constant value. Can't be updated.
  • := - short variable declaration. Can be used only inside a function. Called Syntax sugar. We can't use it to declare a global variable.

Data Types

When we declare with a value we don't need to specify the type. Go will infer the type from the value. But when we declare without a value we need to specify the type.

Eg:

var name string

name = "John"
  • bool - it can be either true or false. Example: true

  • string - string of characters. Example: "Hello, World!"

  • int - integer number. Example: 42

  • float64 - floating point number. Example: 3.14

  • uint - unsigned integer. Example: 42

  • byte - alias for uint8. Example: 42

  • When we declare a variable without a value, Go will assign a default value to it. Eg: 0 for int, false for bool etc.

  • If we don't put a variable type go will automatically assign the type based on the value.-

String

  • A string is a sequence of characters. We can use double quotes to create a string.
var name = "John"
  • We can use backticks to create a raw string literal. It is used to create a string without escape sequences.
var name = `John\n`

We can perform a lot of operations on strings like concatenation, length, indexing etc. Here are some examples:

var name = "John"

fmt.Println(len(name)) // it will print the length of the string
fmt.Println(name[0]) // it will print the first character of the string
fmt.Println(name + " Doe") // it will concatenate the strings

With the strings package, we can perform more operations on strings like splitting, joining, replacing etc. It's one of most used packages in Go.

import "strings"

var name = "John Doe"

fmt.Println(strings.Split(name, " ")) // it will split the string based on the space
fmt.Println(strings.Join([]string{"John", "Doe"}, " ")) // it will join the strings with a space
fmt.Println(strings.Replace(name, "John", "Jane", 1)) // it will replace the first occurrence of John with Jane
fmt.Println(strings.Contains(name, "John")) // it will check if the string contains John
fmy.Println(strings.ToLower(name)) // it will convert the string to lowercase
fmt.Println(strings.ToUpper(name)) // it will convert the string to uppercase

Scan

fmt.Scan reads text from standard input, scanning the text read into successive arguments. Newlines count as space. It returns the number of items successfully scanned. If that is less than the number of arguments, err will report why.

fmt.Scan(&name)

Scanning though bufio

bufio package implements a buffered reader that may be useful both for its efficiency with many small reads and because of the additional reading methods it provides.

func main() {

reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter text: ")
text, _ := reader.ReadString('\n') // It is text, err syntax. We put _ to ignore the error.
fmt.Println(text)
}

"text , err syntax"

In the above example we used text, err syntax. It is a common way to handle errors in Go. If we don't want to handle the error we can use _ to ignore it.

text, err := reader.ReadString('\n')

if err != nil {
panic(err)
}
  • panic - It is a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller.

Conversion

We can convert a value from one type to another. The expression T(v) converts the value v to the type T. We can use strconv package to convert a data type to another.

var num = 42
var stringNum = "42"

var str = strconv.Itoa(num) // it will convert the integer to a string
var intNum, _ = strconv.Atoi(stringNum) // it will convert the string to an integer

Time

We can use time package to get the current time.

t := time.Now()
fmt.Println(t)

Pointers

A pointer is a variable that stores the memory address of another variable. We can declare a pointer by using * operator. Eg:

var p *int

We can get the memory address of a variable using & operator.

var name = "John"
fmt.Println(&name) // it will print the memory address of the variable name

We can get the value of a pointer using * operator. Called dereferencing.


var name = "John"
var myName = &name

fmt.Println(*myName) // it will print the value of the variable name
  • *int - the type *int is a pointer to an int.
var name = "John"
fmt.Println(&name) // it will print the memory address of the variable name

Arrays

An array is a numbered sequence of elements of a single type with a fixed length. We can store a fixed size collection of elements of the same type.

We declare an array as follows:

var arr [5]int  // array of 5 integers

arr[0] = 1
arr[1] = 2

Slice

In this we don't need to specify the size of the array. It is a dynamically sized, flexible view into the elements of an array.

var names []int  // slice of integers

Unlink arrays, we don't add elements to a slice using arr[index] = value. We use append function to add elements to a slice.

names = append(names, 1)
names = append(names, 2)

Loops

In Go, there is only one looping construct, the for loop.

for i := 0; i < 5; i++ {
fmt.Println(i)
}
  • range - The range form of the for loop iterates over a slice or map.
names := []string{"John", "Paul", "George", "Ringo"}

for i, name := range names {
fmt.Println(i, name)
}
  • _ - The blank identifier is a special identifier that is used to ignore values when multiple values are returned from a function. It is used to ignore the index in the above example.
for _, name := range names {
fmt.Println(name)
}

If Else

if num > 0 { // this condition will only be true if num is greater than 0
fmt.Println("Positive")
} else if num == 0 { // this condition will only be true if num is equal to 0
fmt.Println("Equal to zero")
} else { // this condition will only be true if num is less than 0
fmt.Println("Negative")
}

We can check also write data == false or !data

Break and Continue

  • break - The break statement terminates the loop or switch statement and transfers execution to the statement immediately following the loop or switch.

for i := 0; i < 5; i++ {
if i == 3 {
break
}
fmt.Println(i)
}
  • continue - The continue statement terminates the current iteration of the loop, and resumes execution at the next iteration. It can be used only within an iterative or switch statement and only within the body of that statement.
for i := 0; i < 5; i++ {
if i == 3 {
continue
}
fmt.Println(i)
}

Switch

The switch statement is a shorter way to write a sequence of if - else statements. It runs the first case whose value is equal to the condition expression.

switch num {
case 1:
fmt.Println("One")
case 2:
fmt.Println("Two")
default:
fmt.Println("Other")
}

Fallthrough

In Go, we don't need to write break after each case. It will automatically break after each case. If we want to execute the next case we can use fallthrough keyword.

switch num {
case 1:
fmt.Println("One")
fallthrough // it will execute the next case
case 2:
fmt.Println("Two")
default:
fmt.Println("Other")
}

Functions

  • A function is a block of code that performs a specific task. It is a reusable piece of code.
func add(x int, y int) int { // We can specify the type of the parameters
return x + y
}

func main() {
fmt.Println(add(1, 2)) // We can call the function by passing the arguments
}
  • A function can return multiple values.
func swap(x, y string) (string, string) { // When we have more than one return value we put them in parenthesis ().
return y, x
}

a, b := swap("hello", "world") // We can get the return values using multiple assignment

Anonymous functions

  • We can declare a function without a name. Such functions are called anonymous functions.
func(x, y int) int {
return x + y
}

Methods

A method is a function with a special receiver argument. The receiver appears in its own argument list between the func keyword and the method name. Receiver can be of any type. Receiver is a special type of parameter that is passed to a method. It is similar to this in other languages. It is used to access the fields and methods associated with the Type like a Struct. There are two types of receivers:

  1. Value Receiver: It is used when we don't want to modify the original value.

func (t Test) printName() { fmt.Println(t.Name) }

  1. Pointer Receiver: It is used when we want to modify the original value.

func (t *Test) printName() { fmt.Println(t.Name) }

Here is an example of a method:

type Person struct {
name string
}

func (p Person) getName() string { // We can use the receiver argument to access the fields of the struct
return p.name
}

func main() {
p := Person{name: "John"}
fmt.Println(p.getName())
}

Defer

  • A defer statement defers the execution of a function until the surrounding function returns.
func main() {
defer fmt.Println("world") // It will print "world" after the main function returns

fmt.Println("hello")
}

Mutex

  • Mutex is a mutual exclusion lock. The zero value for a Mutex is an unlocked mutex.
var mutex sync.Mutex

mutex.Lock() // It will lock the mutex
mutex.Unlock() // It will unlock the mutex
  • RWMutex is a reader/writer mutual exclusion lock. The lock can be held by an arbitrary number of readers or a single writer. When a writer is active, no readers can be active.
var rwMutex sync.RWMutex

rwMutex.RLock() // It will lock the mutex for reading
rwMutex.RUnlock() // It will unlock the mutex for reading

rwMutex.Lock() // It will lock the mutex for writing
rwMutex.Unlock() // It will unlock the mutex for writing

Maps

  • A map is an unordered collection of key-value pairs. Maps are similar to dictionaries in Python. The limitation of maps is that the key should be of the same type and the value should be of the same type. The key and value can be of any type.
var cars = make(map[string]string, 0) // map of string to string

cars["Toyota"] = "Camry" // adding a key-value pair

delete(cars, "Toyota") // deleting a key-value pair

Structs

  • A struct is a collection of fields. It is a data structure that lets us bundle together related data and behavior. We can use structs to represent real-world objects. It can handle multiple data types. It is similar to classes in other languages.
type Person struct {
name string
age int
}

Go routines and WaitGroup

  • A goroutine is a lightweight thread managed by the Go runtime. We can create a goroutine using the keyword go. It is similar to threads in other languages. The purpose of a goroutine is to run a function concurrently with other functions.
go func() {
fmt.Println("Hello")
}()
  • If the main function exits, the program will exit immediately even if the goroutine is still running. To prevent this, we can use the WaitGroup type. For Eg:

WaitGroup

  • A WaitGroup waits for a collection of goroutines to finish. The main function will wait for the goroutines to finish before exiting.

var wg sync.WaitGroup

func main() {
wg.Add(1) // We are adding 1 to the WaitGroup
go sayHello()
wg.Wait() // We are waiting for the WaitGroup to become zero

}
func sayHello() {
fmt.Println("Hello")
wg.Done() // We are decrementing the WaitGroup by 1
}

Add() increments the WaitGroup counter by 1 and Done() decrements the WaitGroup counter by 1.

Concurrency vs Parallelism

  • Concurrency - It is the ability of a program to be decomposed into parts that can run independently of each other. It is the composition of independently executing processes. It is about dealing with lots of things at once.

  • Parallelism - It is the ability of a program to run multiple tasks simultaneously. It is about doing lots of things at once.

Math

  • rand.Seed() - It is used to initialize the default Source to a deterministic state. If Seed is not called, the generator behaves as if seeded by Seed(1). It should only be called once. It is usually called before the first call to Intn or Float64.
rand.Seed(time.Now().UnixNano())

Json

  • We can use the json package to encode and decode JSON data.

  • json.Marshal() - It is used to encode a value to JSON. It returns a byte slice and an error.

  • json.MarshalIndent() - It is used to encode a value to JSON with indentation. It returns a byte slice and an error.

  • json.Unmarshal() - It is used to decode a JSON-encoded value. It returns an error.

In the Structs we can use the json tag to specify the name of the field in the JSON. I


type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}

Channels

  • A channel is a communication mechanism that allows one goroutine to pass values of a specified type to another goroutine. It is communication between goroutines. It is similar to pipes in other languages.

  • make() - It is used to create a channel. It takes the type of the channel as an argument.

ch := make(chan int)

go func() {
ch <- 10
}()

val := <-ch
fmt.Println(val)

We can create a buffered channel by passing the buffer size as the second argument to the make() function. By default, the channel is unbuffered and can only hold one value. So, if we try to send multiple value to the channel it will give an error.

Buffered Channel

A buffered channel is a channel with a buffer. It can hold multiple values. We can specify the buffer size when we create the channel.

var ch = make(chan int, 5) // buffered channel with a buffer size of 5

Unbuffered Channel

An unbuffered channel is a channel without a buffer. It can hold only one value. We can send a value to the channel only if there is a goroutine ready to receive the value.

var ch = make(chan int) // unbuffered channel

Closing a Channel

We can close a channel using the close() function. It is used to indicate that no more values will be sent on the channel.

msg := make(chan int)

go func() {
msg <- 1
close(msg) // After closing the channel we can't send any more values
}()

But here a catch even tho channel is closed we can still receive the values from it like zero, so it's dalmatic whether the it's channel is closed or the value is zero. To overcome this we can receive the value and a boolean value which will tell us whether the channel is closed or not.

package main

import (
"fmt"
"sync"
)

var wait = sync.WaitGroup{}

func main() {

myChannel := make(chan int, 1)

wait.Add(2)
go func() {
fmt.Println("First Go Routine")
wait.Done()

hello, isChannelOpen := <- myChannel

if !isChannelOpen {
fmt.Println("Channel Closed")
}
fmt.Println(hello)
}()

go func() {
fmt.Println("Second Go Routine")
close(myChannel)
wait.Done()
// myChannel <- 5
}()

wait.Wait()
}

Send Only Channel

var ch = make(chan<- int) // send only channel
go func (ch chan<- int) {
ch <- 10
}(ch)

Receive Only Channel

var ch = make(<-chan int) // receive only channel
go func (ch <-chan int) {
val := <-ch
fmt.Println(val)
}(ch)

IIF's (Immediately Invoked Functions)

  • An immediately invoked function is a function that is executed as soon as it is created. It is a function that is executed immediately after it is created. It is also known as a self-invoking function.
func main() {
func() {
fmt.Println("Hello")
}()
}

Error Handling

In Go, errors are values. We can use the error type to represent an error. We can use the errors.New function to create a new error. It returns an error.

func divide(x, y int) (int, error) {
if y == 0 {
return 0, errors.New("division by zero")
}
return x / y, nil
}

goto

  • The goto statement transfers control to the labeled statement. It is similar to the break statement in other languages. It is used to transfer control to a different part of the program.
func main() {
i := 0
start:
fmt.Println(i)
i++
if i < 5 {
goto start
}
}

Scope rules

  • Local variables are scoped to the function in which they are declared. They are not visible outside the function.
  • Package level variables are scoped to the package in which they are declared. They are visible to all the functions in the package.
  • Exported variables are scoped to the package in which they are declared. They are visible to all the functions in the package and other packages that import the package.

Package level variables

  • We can declare variables at the package level. They are called package level variables.

NOTE: We can't use the short variable declaration operator := to declare package level variables.

var name string = "John" // It will be available to all the functions in the package

func main() {
fmt.Println(name)
}

Expoting and Importing

We can export a function/ variable by capitalizing the first letter of the function/ variable name. Now we can use it in other packages.

func Add(x, y int) int {
return x + y
}

var Name string = "John"

Code Organization

We can organize our code by putting functions/ variables in different files and can use them in the main file or calling the main function from other files.

Also, we can have multiple packages in a single directory.

What's next?