The Go-lang hand book

The Go-lang hand book

Simple yet Powerful

·

25 min read

Go, also known as Golang, is a modern programming language developed by Google in 2009. It is a compiled, statically typed language that is designed to be simple, reliable, and efficient. Go is used to build a wide range of software applications, including web servers, distributed systems, and command-line tools.

Advantages

  • Simple: Go is a simple language to learn and use. It has a small syntax and a small standard library. This makes it easy to get started with Go and to write code that is easy to read and maintain.

  • Reliable: Go is a reliable language. It has several features that make it less prone to errors, such as garbage collection and type safety.

  • Efficient: Go is an efficient language. It compiles to native machine code and has several features that make its code fast, such as goroutines and channels.

  • Scalable: Go is a scalable language. It is designed to support large-scale distributed systems.

  • Modern: Go is a modern language. It has several features that make it well-suited for modern software development, such as concurrency support and generics.

The Hello World

Below is the code for writing Hello world in golang.

package main

import "fmt"

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

//output
//Hello world

Let's break down the code

package main

The package in Go which we write in every Go file is the package declaration. This declaration is the first line of code in a Go file and it tells the Go compiler which package the file belongs to.

Packages are a way to organize Go code into modules. Each Go file belongs to a package and all of the code in a package is compiled together. Packages can be imported into other Go files using the import statement.

The package declaration must be the first line of code in a Go file and it must be followed by the name of the package. The package name must be unique and it cannot be the same as the name of any other package on your system.

import "fmt"

The fmt package provides Println,Printf,Print

Here we are using Println to print contents to the console.

Inside Println we can pass string/var/const . whatever the content we want to print to the console.

Variables

package main

import "fmt"

//making the first letter capital makes the variable or constant public

const LoginToken string="vsusuwsnuwshwns"

func main(){
    var username string="Vishnu"
    fmt.Println(username)
    fmt.Printf("Variable is of type: %T \n",username)

    var value uint8=56
    fmt.Println(value)
    fmt.Printf("Variable is of type: %T\n",value)

    var isTrue=false
    fmt.Println(isTrue);
    fmt.Printf("Variable is of type: %T\n",isTrue)

    var smallFloat float32=56.937378383
    fmt.Println(smallFloat)
    fmt.Printf("Variable is of type: %T\n",smallFloat)

    var anotherVariable int

    fmt.Println(anotherVariable)
    fmt.Printf("Variable is of type: %T\n",anotherVariable)


    var sampleString string
    fmt.Printf(sampleString)
    fmt.Printf("Variable is of type: %T\n",sampleString)

    //implicit types

    var name="Tommy"
    fmt.Println(name)

    //no var style
    numberOfUser :=300000
    fmt.Println(numberOfUser);

    fmt.Println(LoginToken);
    fmt.Printf("Variable is of type: %T\n",LoginToken)


}

In the above code, we can see %T This is a format verb which is used to print type of the data. The same as %v format verb is used to get data itself.

In Golang there are different types of data we can use to store data. Below are the data types

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // alias for uint8

rune // alias for int32
     // represents a Unicode code point

float32 float64

complex64 complex128

UserInput

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main(){
    welcome:="Welcome to user input"
    fmt.Println(welcome)

    reader:=bufio.NewReader(os.Stdin)
    fmt.Println("Enter the rating for our Pizza:");

    //comma ok syntax || error ok syntax

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

    fmt.Println("Thanks for rating",input)
    fmt.Printf("Type of rating %T",input)

}

In the above example, we can see := operator. It is used for short declaration and assignment of the variable. Where as = is used as a normal assignment variable

In the above example, we are using two packages os and bufio to read the user input from the console.

Here we are creating a newreader with help of bufio which takes inputs from os.Stdin which allows reading inputs from the keyboard

Here we will introduce the try catch of golang called comma ok syntax

input,err:=reader.ReadString('\n') Here the left function may return the output or error. So we are using two variables to handle both output and err.

In the input variable we will get the user input which we can print to the console.

Conversions

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main(){
    fmt.Println("Welcome to Pizza app")
    fmt.Println("Please rate our Pizza between 1 and 5")
    reader:=bufio.NewReader(os.Stdin)

    input,_:=reader.ReadString('\n')
    fmt.Println("Thanks for rating",input)


    numRating,err:=strconv.ParseFloat(strings.TrimSpace(input),64)

    if err !=nil{
        fmt.Println(err)
    }else{
        fmt.Println("Added 1 to your rating:" ,numRating+1)
    }

}

In the above example, we are converting userinput (string) to float. As the userinput always be string, we are trying to convert it to float,int or any as required.

We are using strconv to convert the string to float and strings.TrimSpace to trim the white spaces in userinput

Random Number

package main

import (
    "fmt"
    "math/big"

    //"math/rand"
    "crypto/rand"
)

func main(){
    fmt.Println("Welcome to maths in golang")

     //var mynumberOne int=2
    //var mynumberTwo float64=4

    // fmt.Println("The sum is :", mynumberOne+int(mynumberTwo))

    //random number


    //fmt.Println(rand.Intn(5))




}

In the above example, we are using rand from math

With math : we are using Intn(n) which will return us a random number till n

Time

package main

import (
    "fmt"
    "time"
)


func main(){

    fmt.Println("Welcome to time in golang")

    presentTime:=time.Now()
    fmt.Println(presentTime);

    fmt.Println(presentTime.Format("01-02-2006 15:04:05 Monday"))

    createdDate:=time.Date(2020,time.December,10,23,23,0,0,time.UTC)
    fmt.Println(createdDate)
    fmt.Println(createdDate.Format("01-02-2006 Monday"))

}

In the above example, we have used the time package to get present time from our system. There are different formatting for print time in Golang.

"01-02-2006 15:04:05 Monday"

This is the ideal time should use in Golang to format time and date.

We can create our own timestamp with time.Date() function

Pointers

A pointer in Go is a variable that stores the memory address of another variable. Pointers are useful for passing references to values and records within your program.

To declare a pointer, you use the * operator followed by the type of the value that the pointer will point to. For example, the following code declares a pointer to an integer:

var p *int

To assign a value to a pointer, you use the & operator followed by the variable that you want to point to. For example, the following code assigns the address of the variable x to the pointer p:

x := 10
p = &x

Once you have a pointer to a value, you can use the * operator to dereference the pointer and access the value that it points to. For example, the following code prints the value of the variable x to the console:

fmt.Println(*p)
//output
//10

Pointers can also be used to modify the values of variables. For example, the following code increments the value of the variable x by 1:

*p++
//above expression is equivalent to below one
x++

Pointers can be useful in a variety of situations. For example, pointers can be used to:

  • Pass references to values to functions.

  • Return references to values from functions.

  • Create linked lists and other data structures.

  • Implement efficient algorithms.

However, it is important to use pointers carefully, as they can be dangerous if used incorrectly. For example, if you dereference a nil pointer, your program will crash.

Here is an example of how to use pointers in a function:

func increment(p *int) {
  *p++
}

func main() {
  x := 10
  increment(&x)

  fmt.Println(x)
}
//output
//11

In this example, the increment() function takes a pointer to an integer as its argument. The function then dereferences the pointer and increments the value that it points to.

Pointers can be a powerful tool, but it is important to use them carefully. If you are new to Go, I recommend reading the documentation on pointers before using them in your code.

Arrays

In golang we are not going to use arrays much we delegating all the usual functionalities of arrays to slices in golang

package main

import "fmt"

func main(){
    fmt.Println("Welcome to array in golangs")


    var fruitList [5]string

    fruitList[0]="Apple"
    fruitList[1]="Orange"
    fruitList[2]="Banana"
    fruitList[3]="Strawberry"
    fruitList[4]="Pineapple"


    fmt.Println("Fruit List is ",fruitList)
    fmt.Println("Fruit List is ",len(fruitList))
    var vegList =[5]string{"tomato","potato","beans"}
    fmt.Println("Veg list",vegList)
}

len() is used to get the length of the array

In the above example we have used array index to assign values to and there is other syntax to assign while declaration in vegList example

Slices

package main

import (
    "fmt"
    "sort"
)

func main() {
    fmt.Println("This is on slices")
    var fruitList = []string{"Apple", "Banana", "Peach"}
    fmt.Printf("Type of fruitList %T\n", fruitList)

    fruitList = append(fruitList, "Mango", "Tomato")
    fmt.Println(fruitList)

    fruitList = append(fruitList[1:])
    fmt.Println(fruitList)

    highScores := make([]int, 4)
    highScores[0] = 244
    highScores[1] = 944
    highScores[2] = 744
    highScores[3] = 544
    //highScores[4]=000  this doesnt work

    highScores = append(highScores, 666, 888, 999)
    fmt.Println(highScores)

    sort.Ints(highScores)
    fmt.Println(highScores)
    fmt.Println(sort.IntsAreSorted(highScores)) // returns boolean

    //remove a value from slice
    var courses = []string{"react", "javascript", "swift", "python"}
    fmt.Println(courses)
    var index int = 2
    courses = append(courses[:index], courses[index+1:]...)
    fmt.Println(courses)
}

In the above example we are using slices.

var fruitList = []string{"Apple", "Banana", "Peach"} This is how we declare slices in golang and assign values to it.

For making slices we can use memory allocation functions like make to create fixed memory storage to store slice values as shown in the above example

But we can use append more values to a slice even if there is confined memory allocated to a slice.

courses = append(courses[:index], courses[index+1:]...) This is used to delete a value in a slice at a specific index.

Memory allocation

The new() and make() keywords in Go are used to allocate memory and create new values. However, they work differently and are used for different purposes.

new()

The new() keyword allocates memory for a new value of the specified type and returns a pointer to that value. The type of the value must be specified when the new() keyword is used. For example, the following code allocates memory for a new integer value and returns a pointer to that value:

p := new(int)

The new() keyword can be used to allocate memory for any type of value, including arrays, slices, maps, and structs.

make()

The make() keyword is used to create new slices, maps, and channels(we will discuss). The type of the slice, map, or channel must be specified when the make() keyword is used. For example, the following code creates a new slice of integers:

s := make([]int, 0)

The make() keyword can also be used to create a new slice or map with a specific capacity. For example, the following code creates a new slice of integers with a capacity of 10 elements:

s := make([]int,0, 10)

The line of code s := make([]int, 0, 10) declares a new slice of integers with a capacity of 10 elements.

The make() function is used to create new slices, maps, and channels. The first argument to the make() function is the type of the slice, map, or channel to be created. In this case, the first argument is []int, which means that we are creating a slice of integers.

The second argument to the make() function is the initial length of the slice, map, or channel. In this case, the second argument is 0, which means that the slice will be initially empty.

The third argument to the make() function is the capacity of the slice, map, or channel. The capacity is the maximum number of elements that the slice, map, or channel can hold without having to be reallocated. In this case, the third argument is 10, which means that the slice can hold up to 10 elements without having to be reallocated.

When to use new() and make()

The new() keyword should be used to allocate memory for new values of any type, including arrays, slices, maps, and structs. The make() keyword should be used to create new slices, maps, and channels.

Here is a table that summarizes the difference between new() and make():

OperatorUsage
new()Allocates memory for a new value of the specified type and returns a pointer to that value.
make()Creates a new slice, map, or channel and allocates memory to it

Array vs Slice

The main difference between a slice and an array in Go is that a slice is a reference to a contiguous segment of an array, while an array is a fixed-size data structure.

Arrays

Arrays are fixed-size data structures that store a series of values of the same type. The size of an array is specified when it is declared. For example, the following code declares an array of 10 integers:

Go

var arr [10]int

Once an array is declared, its size cannot be changed. To add or remove elements from an array, you must copy the array to a new array of a different size.

Slices

Slices are references to contiguous segments of arrays. Slices can be of any size, up to the size of the underlying array. To create a slice, you use the [:] operator followed by the array that you want to slice. For example, the following code creates a slice of the first 5 elements of the array arr:

Go

s := arr[:5]

Slices can be resized using the append() function. For example, the following code appends the value 11 to the end of the slice s:

Go

s = append(s, 11)

Slices can also be copied using the copy() function. For example, the following code copies the slice s to the slice t:

Go

t := make([]int, len(s))
copy(t, s)

When to use slices and arrays

Slices are generally preferred over arrays because they are more flexible and dynamic. Slices can be resized and copied easily, and they can also be passed to functions as arguments.

Arrays are typically used when you need to store a fixed-size amount of data. For example, you might use an array to store the coordinates of a pixel on a screen.

Here is an example of how to use slices and arrays in a function:

Go

func printSlice(s []int) {
  for _, v := range s {
    fmt.Println(v)
  }
}

func main() {
  arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
  s := arr[:5]

  printSlice(s)
}

Output:

1
2
3
4
5

In this example, the printSlice() function takes a slice of integers as its argument and prints the elements of the slice to the console.

The main() function declares an array of 10 integers and creates a slice of the first 5 elements of the array. The printSlice() function is then called with the slice as its argument.

Slices and arrays are both powerful data structures in Go. It is important to understand the difference between them so that you can choose the right data structure for your needs.

Maps

package main

import "fmt"

func main() {
    fmt.Println("Maps in golang")
    languagues := make(map[string]string)
    languagues["JS"] = "javascript"
    languagues["RB"] = "Ruby"
    languagues["PY"] = "Python"

    fmt.Println("List of all languages", languagues)
    fmt.Println("JS shorts for", languagues["JS"])
    delete(languagues, "RB")
    fmt.Println("List of all languages", languagues)

    //loops

    for key, value := range languagues {
        fmt.Printf("For key %v,value is %v\n", key, value)
    }
}

The maps are key-value pairs.

languages:=make(map[string]string) This is how we declare a map. We can use what ever data type we want in place of string for key-value pairs.

Assign values to maps

languagues["JS"] = "javascript" This is how we assign values to a map using the keys and values.

Delete a vale from a map

delete(languages, "RB") We will pass the map and key to the delete function to delete a specific key-value pair from a map.

Struct

package main

import (
    "fmt"
)

func main() {

    //there is no inheritence in structs in golang
    fmt.Println("This is structs in golang")
    Tommy := User{"Tommy", "powusgsg@gmail.com", true, 24}
    fmt.Println(Tommy)
    fmt.Printf("Tommy details are:%+v\n", Tommy)
    fmt.Printf("Name is %v and email is %v", Tommy.Name, Tommy.Email)

}

type User struct {
    Name   string
    Email  string
    status bool
    Age    int
}

A struct definition in Golang is a way to create a new type that combines multiple fields of different types. Structs are useful for grouping related data together into a single unit.

This is the syntax to declare a struct

type User struct {
    Name   string
    Email  string
    status bool
    Age    int
}

We can create new objects from struct and assign values to as below

Tommy := User{"Tommy", "powusgsg@gmail.com", true, 24}

The Object values can be used as shown below

fmt.Printf("Name is %v and email is %v", Tommy.Name, Tommy.Email)

If else

package main

import "fmt"

func main() {
    fmt.Println("if else in golang")

    loginCount := 10
    var result string
    if loginCount < 10 {
        result = "Regular user"
    } else if loginCount > 10 {
        result = "Watch out"
    } else {
        result = "Exactly 10 login count"
    }
    fmt.Println(result)
    if 9%2 == 0 {
        fmt.Println("Number is even")
    } else {
        fmt.Println("Number is odd")
    }

    if num := 3; num < 10 {
        fmt.Println("Number is less than 10")
    } else {
        fmt.Println("Number is greater than 10")
    }
}

The if else in Golang is the same as in other languages but there is a new syntax where we can declare and initialize a variable and check for conditions as shown below

if num := 3; num < 10 {
        fmt.Println("Number is less than 10")
    } else {
        fmt.Println("Number is greater than 10")
    }

Switch case

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    fmt.Println("Switch and case in golang")

    diceNumber := rand.Intn(6) + 1
    fmt.Println("Value of dice is ", diceNumber)

    switch diceNumber {
    case 1:
        fmt.Println("Dice value is 1 and you can open")
    case 2:
        fmt.Println("Dice value is 2 move to spot 2")
    case 3:
        fmt.Println("Dice value is 3 move to spot 3")
    case 4:
        fmt.Println("Dice value is 4 move to spot 4")
    case 5:
        fmt.Println("Dice value is 5 move to spot 5")
    case 6:
        fmt.Println("Dice value is 6 roll again!")
    default:
        fmt.Println("what is what!")
    }

}

In the above example, we have created a dice game using a switch case and rand function.

Switch in Golang is the same as in other languages where we have a variable according to the variable value we will switch the condition and execute.

Loops

package main

import "fmt"

func main() {
    fmt.Println("Welcome to loops in golang")

    day := []string{"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"}
    fmt.Println(day)

    // for d := 0; d < len(day); d++ {
    //     fmt.Println(day[d])
    // }

    // for i := range day { //in i the index is stored not values
    //     fmt.Println(day[i])
    // }

    for index, day := range day {
        fmt.Printf("index is %v, value is %v\n", index, day)
    }

    for _, day := range day {
        fmt.Printf("value is %v\n", day)
    }

    rogueValue := 1

    for rogueValue < 10 {
        if rogueValue == 2 {
            goto medo
        }
        if rogueValue == 5 {
            rogueValue++
            continue
        }
        fmt.Println("Value is :", rogueValue)
        rogueValue++
    }
medo:
    fmt.Println("I am in medo")
}

In the above example, we have traditional for-loop syntax along with new syntaxes as discussed below

for index, day := range day {
        fmt.Printf("index is %v, value is %v\n", index, day)
    }

In the above example index and value are stored and passed to control flow for looping to slice.

for rogueValue < 10 {
        if rogueValue == 2 {
            goto medo
        }
        if rogueValue == 5 {
            rogueValue++
            continue
        }
        fmt.Println("Value is :", rogueValue)
        rogueValue++
    }
medo:
    fmt.Println("I am in medo")

In the above example, we are using for loop as a while structure where we have only used condition to loop

goto The goto is used in loops to move the control flow from the loop to execute something else as shown above when the condition is true medo is executed

Functions

package main

import "fmt"

func main() {
    fmt.Println("Welcome to functions in golang")
    greeter()

    result := adder(3, 5)
    fmt.Println("Result is ", result)

    proResult, mymessage := proAdder(1, 2, 3, 4, 5, 6, 7)
    fmt.Println("ProResult is", proResult, mymessage)

}

func adder(valOne int, valTwo int) int {
    return valOne + valTwo
}

func proAdder(values ...int) (int, string) {
    total := 0

    for _, val := range values {
        total += val
    }
    return total, "hello world"
}

func greeter() {
    fmt.Println("Namaste from golang")
}

The function is reusable code which will be called when required.

In Golang we have functions the same as in other languages we need to mention the return type if present and the arguments type.

...int this dot operator is used to get multiple values from function parameters.

A function can also have two returns as shown below

return total, "hello world"

At this time these returns types must be mentioned in func definition.

Methods

package main

import (
    "fmt"
)

func main() {

    //there is no inheritence in structs in golang
    fmt.Println("This is structs in golang")
    vishnu := User{"Vishnu", "vishnuavm5@gmail.com", true, 24}
    fmt.Println(vishnu)
    fmt.Printf("vishnu details are:%+v\n", vishnu)
    fmt.Printf("Name is %v and email is %v\n", vishnu.Name, vishnu.Email)
    vishnu.GetStatus()
    vishnu.NewMail()

}

type User struct {
    Name   string
    Email  string
    status bool
    Age    int
}

// method syntax
func (u User) GetStatus() {
    fmt.Println("Is user active:", u.status)

}

func (u User) NewMail() { //this doesnt change the original value its only the copy of the value
    u.Email = "test@go.dev"
    fmt.Println("Email of this user is ", u.Email)
}

Methods are functions that are created for structs and used by struct objects

Below is the syntax to declare /define a method

func (u User) GetStatus() {
    fmt.Println("Is user active:", u.status)

}

Any manipulation by method is not reflected in the original value.

The Methods can be built using struct or non struct but not the int or string type and the type should be present in same file

package main

import "fmt"

// using struct
type Person struct{
name string
age int
}
//thie Person is receiver where we can access the person details in display method
func (p Person) display(){
fmt.Println(p.name)
fmt.Println(p.age)
}

func main(){
person:= Person{name:'Vishnu',age:26}
person.display()
}
//output
Vishnu
26

Using Non struct types

package main

import "fmt"

//non struct type
type number int

func (n number) square() number{
return n*n
}

func main(){
num:=number(5)
fmt.Println(num.square())
}

// output
25

Using the pointer types

package main

import "fmt"

type Employee struct{
name string
age int
}
// sending pointer as reciver to make changes at ref not a copy
func (e *Employee) changeName(changedName string){
e.name=changedName
}

func (e Employee) changeNameWithOutPointer(cName string){
e.name=cName
}

func main (){
emp:= Employee{name:"Vishnu",age:26}
fmt.Println("Before",emp.name)
emp.changeName("Vish")
emt.Println("After",emp.name)

empwithoutpointer:= Employee{name:"Anusha",age:26}
fmt.Println("Before",empwithoutpointer.name)
empwithoutpointer.changeNameWithOutPointer("anu")
fmt.Println("After",empwithoutpointer.name)


}
//ouput
Vishnu
vish
Anusha
Anusha // here this will not change because it has not sent the ref , its send the copy value
// to the reciver so the copied value will be changed but not the original ref value

Defer

The defer in Go is used to delay the execution of a function or statement until the surrounding function returns.

package main

import "fmt"

func main() {

    //two,one,world Last in first out
    defer fmt.Println("World")
    defer fmt.Println("One")
    defer fmt.Println("Two")
    fmt.Println("Hello")
    myDefer()
}

func myDefer() {
    for i := 0; i < 5; i++ {
        defer fmt.Println(i)
    }
}

every statement written with defer will be executed last and if too many statements use the defer then the execution is done as last in first out. which means the last statement with defer will be executed first.

Files

package main

import (
    "fmt"
    "io"

    "os"
)

func main() {
    fmt.Println("Welcome to files in golang")
    content := "This needs to go in a file -Vishnu"

    file, err := os.Create("./myfile.txt")

    // if err != nil {
    //     panic(err)
    // }

    checkNilError(err)
    length, err := io.WriteString(file, content)
    // if err != nil {
    //     panic(err)
    // }

    checkNilError(err)
    fmt.Println("Length is :", length)

    defer file.Close()
    readFile("./myfile.txt")
}

func readFile(filename string) {
    dataByte, err := os.ReadFile(filename) //data inside the file is returned as buffer or bytes
    checkNilError(err)

    fmt.Println("Text data inside the file is \n", string(dataByte))

}

func checkNilError(err error) {
    if err != nil {
        panic(err)
    }
}

to create file

file, err := os.Create("./myfile.txt")

to add content to file.

length, err := io.WriteString(file, content)

read data from file

dataByte, err := os.ReadFile(filename)

The data that outputs from file will be in byte we need to convert it to string as shown in the example above

Webreq

package main

import (
    "fmt"
    "io"
    "net/http"
)

const url = "https://lco.dev"

func main() {
    fmt.Println("golang web request")

    response, err := http.Get(url)

    if err != nil {
        panic(err)
    }
    fmt.Printf("Response if of type:%T\n", response)
    defer response.Body.Close() //closing is mandatory

    dataBytes, err := io.ReadAll(response.Body)
    if err != nil {
        panic(err)
    }

    content := string(dataBytes)
    fmt.Println(content)

}

In this example, we are using http package to send and receive web requests

We are getting responses from url from http get method

response, err := http.Get(url)

Closing the response is mandatory

defer response.Body.Close()

io package is used to read the response as shown below

dataBytes, err := io.ReadAll(response.Body)

Structuring and destructuring Url

package main

import (
    "fmt"
    "net/url"
)

const myurl string = "https://lco.dev:3000/learn?coursename=reactjs&paymentid=ghbj52jsn"

func main() {
    fmt.Println("Url in golang")
    fmt.Println(myurl)

    //parsing
    result, _ := url.Parse(myurl)

    // fmt.Println(result.Scheme)
    // fmt.Println(result.Host)
    // fmt.Println(result.Path)
    // fmt.Println(result.Port())
    // fmt.Println(result.RawQuery)

    qparams := result.Query()

    fmt.Printf("the type of query parms %T\n", qparams)
    fmt.Println(qparams["coursename"])
    for _, value := range qparams {
        fmt.Println("Param is :", value)
    }

    partsOfUrl := &url.URL{
        Scheme:  "https",
        Host:    "lco.dev",
        Path:    "/tutcss",
        RawPath: "user=vishnu",
    }

    anotherURL := partsOfUrl.String()
    fmt.Println(anotherURL)

}

url package is used to read or write urls

as shown below we can use parse to get all parts of url

result, _ := url.Parse(myurl)

url is also used to build urls as shown below

partsOfUrl := &url.URL{
        Scheme:  "https",
        Host:    "lco.dev",
        Path:    "/tutcss",
        RawPath: "user=vishnu",
    }

Go routines

In this going to learn about multi threading in go lang

concurrency and Parlellism

In concurrency, we are using only 1 core to execute a task which takes time but in parallelism, we will be using n no of cores to run tasks and save time

package main

import (
    "fmt"
    "net/http"
    "sync"
)

var signals = []string{"test"}

var wg sync.WaitGroup //pointers usually
var mut sync.Mutex    //pointers usally

func main() {
    fmt.Println("This is for goroutines in golang")
    // go greeter("hello")
    // greeter("World")

    websitelist := []string{
        "https://lco.dev",
        "https://go.dev",
        "https://google.com",
        "https://github.com",
    }

    for _, web := range websitelist {
        go getStatusCode(web)
        wg.Add(1) //
    }
    fmt.Println(signals)
    wg.Wait() // wait till the threads are back

}

// func greeter(s string) {
//     // for i := 0; i < 6; i++ {
//     //     time.Sleep(3 * time.Millisecond)
//     //     fmt.Println(s)

//     // }
// }

func getStatusCode(endpoint string) {
    defer wg.Done() //send singal done
    res, err := http.Get(endpoint)

    if err != nil {
        fmt.Println("Oops in endpoint")
    } else {
        mut.Lock()
        signals = append(signals, endpoint)
        mut.Unlock()
        fmt.Printf("%d status code for %s\n", res.StatusCode, endpoint)

    }
}

For using multiple threads we will use go keywords to assign tasks to different cores

We need to wait till the core comes with the response for that we are using sync package

we have wait add and done

The done is called when the task is done the wait will wait till the done is called the add will count for different cores

Mutex

A mutex in Go is a synchronization mechanism that allows you to control access to shared resources. Mutexes are useful for preventing race conditions, which can occur when multiple goroutines are trying to access the same shared resource at the same time.

To use a mutex, you first need to create a new mutex object. You can do this using the sync.Mutex type. Once you have created a mutex object, you can then use the Lock() and Unlock() methods to control access to the shared resource.

The Lock() method blocks until the mutex is available. Once the mutex is available, the Lock() method returns and the goroutine is allowed to access the shared resource.

The Unlock() method releases the lock on the mutex. Once the lock is released, other goroutines are allowed to access the shared resource.

package main

import (
    "fmt"
    "sync"
)

func main() {
    fmt.Println("Race condition - lco.in")
    wg := &sync.WaitGroup{}
    mut := &sync.Mutex{} //also check RWMutex

    var score = []int{0}
    wg.Add(3)
    go func(wg *sync.WaitGroup, m *sync.Mutex) {

        fmt.Println("one Routine")
        mut.Lock()
        score = append(score, 1)
        mut.Unlock()
        wg.Done()

    }(wg, mut)

    go func(wg *sync.WaitGroup, m *sync.Mutex) {
        fmt.Println("Two Routine")
        mut.Lock()
        score = append(score, 2)
        mut.Unlock()
        wg.Done()
    }(wg, mut)
    go func(wg *sync.WaitGroup, m *sync.Mutex) {
        fmt.Println("Three Routine")
        mut.Lock()
        score = append(score, 3)
        mut.Unlock()
        wg.Done()
    }(wg, mut)

    wg.Wait()
    fmt.Println(score)

}

//command to run the program in race condition < go run --race . >

If we get an error in race conditions then there is a problem with the code.

Channels

Channels in Golang are a way for goroutines to communicate with each other. Channels are similar to queues, but they are more efficient because they are implemented using buffered communication.

To create a channel, you use the chan keyword followed by the type of data that the channel will carry. For example, to create a channel that carries integers, you would use the following code:

ch := make(chan int)

Once you have created a channel, you can send and receive data from the channel using the <- operator. To send data to a channel, you use the following syntax:

ch <- 10

To receive data from a channel, you use the following syntax:

v := <-ch

The <- operator blocks until there is data to send or receive. If the channel is empty, the <- operator will block until there is data to receive. If the channel is full, the <- operator will block until there is room to send data.

Channels can be used to implement a variety of different communication patterns, such as producer-consumer, request-response, and publish-subscribe.

Here is an example of a simple producer-consumer program using channels:

package main

import (
    "fmt"
    "time"
)

func producer(ch chan int) {
    for i := 0; i < 10; i++ {
        ch <- i
        time.Sleep(time.Second)
    }
    close(ch)
}

func consumer(ch chan int) {
    for i := range ch {
        fmt.Println(i)
    }
}

func main() {
    ch := make(chan int)

    go producer(ch)
    go consumer(ch)

    time.Sleep(time.Second * 10)
}

Output:

0
1
2
3
4
5
6
7
8
9

In this example, the producer() function sends 10 integers to the channel. The consumer() function receives the integers from the channel and prints them to the console.

Channels are a powerful tool for communication between goroutines. They are easy to use and can be used to implement a variety of different communication patterns.

Here are some tips for using channels in Golang:

  • Use channels to communicate between goroutines.

  • Use the chan keyword to create a channel.

  • Use the <- operator to send and receive data from a channel.

  • Be careful not to block on a channel for too long. This can block other goroutines from accessing the channel.

  • Use defer statements to ensure that channels are always closed, even if an error occurs.

Channels are an essential tool for any Go programmer. They can help you to write more concurrent and efficient code.

We can also use sync wait groups instead of time as shown below

package main

import (
    "fmt"
    "sync"
)

func main() {
    fmt.Println("Channels in golang -lco.in")
    myChannel := make(chan int, 2)

    wg := &sync.WaitGroup{}

    // myChannel <- 5

    // fmt.Println(<-myChannel)

    wg.Add(2)
    //receive only
    go func(ch <-chan int, wg *sync.WaitGroup) {
        //fmt.Println(<-myChannel)
        //fmt.Println(<-myChannel)
        val, isChanelOpen := <-myChannel

        fmt.Println(isChanelOpen)
        fmt.Println(val)
        wg.Done()
    }(myChannel, wg)
    //send only
    go func(ch chan<- int, wg *sync.WaitGroup) {
        myChannel <- 5
        myChannel <- 6
        close(myChannel)
        wg.Done()
    }(myChannel, wg)

    wg.Wait()

}

//we can only dump value in to channel when there is listener

Hope you had a wonderful Learning

Thank you