GoLang Programming Basics Notes

Ajay TekamAjay Tekam
22 min read

Program Structure

  • Go Program is made up of packages. Packages are equivalent of modules in python.
  • The program execution starts by running main package.
  • Packages are imported using import keyword.

Example Program:

package main

import "fmt"

func main() {
    fmt.Println("Hello from GoLang")
}

Structure of Program

  • package main imports a package main. Go packages are similar to libraries or modules in other language, a package contains one or more go source file. The main package needs to be imported by every go program, because every program starts running in package main.
  • import "fmt" imports the package ΓÇ£fmtΓÇ¥ into the sample program for using the function Println. The package fmt comes from the Go standard library.

Running go Program :

go run main.go

At above program :

  • Println() function from fmt package.
  • In every go program always main function will execute.

Running and Compilation

To run the program go run source_file

go run prg1.go

To compile a binary go build source_file

go build prg1.go

Changing output binary name

go build -o prg1 prg1.go

Above command builds executable file hello. To compile without debug symbols use below command

go build -ldflags '-w -s' prg1.go

Compiling for Cross Architecture, for arm

GOOS=linux GOARCH=arm GOARM=6 go build -o armhello hello.go

For amd64

GOOS=linux GOARCH=amd64 go build -o hello hello.go

Package Imports

Packages are imported using import keyword.

import "fmt"
import "math"

Multiple packages can be imported using bracket.

import (
    "fmt", 
    "math"
)

Functions imported from packages starts with Capital letter.

fmt.Println("Hello world")
fmt.Scan(&var)

List of go packages can be found here : https://pkg.go.dev/std

Variables

There are three ways to defined variable in go

  • Method 01
// defining variables 
var userName string
var age int 

// assigning values 
userName = "Ajay"
age = 28
  • Method 02 Using Type Interference

With :=

userName := "Ajay"
age := 28

The variables type is inferred from the value on the right hand side. With var name = value expression

var userName = "Ajay"
var age = 28
  • Constant variable
const testVar int = 50

or

const testVar = "Ajay"

Creating Custom Packages

Creating Go Project

go mod init exmple/hello
  • exmple/hello is path of the project/code.
  • go.mod file lists the specific versions of the dependencies that your project uses.
  • It is good idea to put the github repo url in project example:
go mod init github.com/ajaytekam/gotest

Example of Creating Go Packege

  • Create a go project
go mod init github.com/ajaytekam/gotest
  • Create two files
// func01.go

package gotest 

import "fmt"

func SayHello01() {
  fmt.Println("Hello world, from func01.go")
}
// func02.go

package gotest

import "fmt"

func SayHello02() {
  fmt.Println("Hello world This message from func02.go")
}
  • There are three total files
func01.go
func02.go
go.mod

Now push the code into github repo

Using the package in program

  • Creating a project
go mod init example/hello
  • Create a file with below code
// main.go
package main

import "github.com/Ajaytekam/gotest"
  • Now run the below command to import the package from github, by doing this the auto-complete function in your editor (LSP) will pickup the defined functions in gotest package
go mod tidy

where tidy flag used to Adds missing and removes unused modules and dependencies from the go.

  • Now add this code into main.go
// main.go

package main

import "github.com/Ajaytekam/gotest"

func main() {
  gotest.SayHello01()
  gotest.SayHello02()
}

Run the application go run .

Creating Binary

To create binary run command go build .

Installing the Binary

To install the binary in default go binary location run go install .

Remove Install Binary

You have to remove the installed binary manually. The defult path for the go binary is ~/.go/bin in linux and ~/.go/bin in linux.

Basic Data Types

Some basics data types are :

  • bool : for boolean values.
  • string : for string values.
  • int : for signed integers. It comes with specific sizes int8, int16, int32, int64.
  • uint : for unsigned int numbers. uint8, uint16, uint32, unit64, uintptr.
  • byte : alias for uint8
  • rune : alias for int32. Represents a unicode char.
  • float32 and float64 : for floating point numbers.
  • complex64 and complex128 : for complex numbers.

For golang datatype visit : https://golangbyexample.com/all-data-types-in-golang-with-examples/

User Input/Output with fmt

Printing Data/Messages :

  • fmt.Println() : prints with appending line termination.
fmt.Println("Hello world")

Example of printing variable with fmt.Println()

package main

import "fmt"

func main() {
    var userName = "Ajay"
    var age = 28
    var occup = "Cloud Engineer"

    fmt.Println("My name is", userName, ".I am", age, "years old.")
    fmt.Println("And i work as a", occup)
}
  • fmt.Printf() : We can use format string to prints variables. Some basics format string are %v which prints variable value, %T which prints variable type and %t which prints only boolean value (true/false).
VerbsDescription
%ddecimal integer
%x,%o,%binteger in hexadecimal,ocatal,binary
%f,%g,%efloating point number
%tboolean:true or false
%crune(unicode code point)
%sstring
%qquoted string ΓÇ£abcΓÇ¥ or rune ΓÇ£cΓÇ¥
%vany value in a natural format
%Ttype of any value
%%literal percent sign(no operand)

For more details check fmt documentation.

User Input :

  • fmt.Scan() : takes user input and store it into variable. It takes variable address (pointer) as an argument &variable_name.
package main

import "fmt" 

func main() {
    // declaring vars 
    var userName string
    var age int 

    // taking user input
    fmt.Printf("Name: ")
    fmt.Scan(&userName)
    fmt.Printf("Age: ")
    fmt.Scan(&age)

    fmt.Printf("My name is %v, i am %v years old\n", userName, age)
}

Documentation for fmt package can be found here : https://pkg.go.dev/fmt

Array and Slices

Array

Creating array:

  • Method 1
var VARIABLE_NAME [SIZE]TYPE

Example:

var numbers [5]int
  • Method 2
var VARIABLE_NAME  = [SIZE]TYPE{Value1, value2, ...value_N}   

// or 

VARIABLE_NAME := [SIZE]TYPE{Value1, value2, ...value_N}

Example:

var nameLists  = [5]string{"john", "david", "peter", "samay", "rammy"}

// or 

nameLists := [5]string{"john", "david", "peter", "samay", "rammy"}

Accessing array elements:

fmt.Println(nameLists)
// Output: [john david peter samay rammy]

fmt.Println(nameLists[0])
// Output: john

fmt.Println(nameLists[2])
// Output: peter

fmt.Println(nameLists[4])
// Output: rammy

fmt.Println(nameLists[1:3])
// Output: [david peter]

fmt.Println(nameLists[2:4])
// Output: [peter samay]

fmt.Println(nameLists[3:])
// Output: [samay rammy]

fmt.Println(nameLists[4:])
// Output: [rammy]
  • len : Return lenth of array.
fmt.Priontln(len(myArray))
  • cap : Return total capacity of an array.
fmt.Println(cap(myArray))
  • range : Used to iterate through array.
for var_1, var2 := range Array_Name {
    fmt.println("Index :", var_1, "| value:", var_2)
}

Where var_1 contains index of element and var_2 contains element value. Example :

package main

import "fmt"

func main() {
    var numx [5]int
    numx[0] = 10
    numx[1] = 20
    numx[2] = 30
    numx[3] = 40
    numx[4] = 50

    for i, j := range numx {
        fmt.Println("Index:", i, "| Value:", j)
    }
}

Output :

Index: 0 | Value: 10
Index: 1 | Value: 20
Index: 2 | Value: 30
Index: 3 | Value: 40
Index: 4 | Value: 50

To print just values with range we can provide an empty var _ in place of index

for _, var2 := range Array_Name {
    fmt.println("Index :", var_1, "| value:", var_2)
}

Example :

package main

import "fmt"

func main() {
    var numx [5]int
    numx[0] = 10  
    numx[1] = 20
    numx[2] = 30
    numx[3] = 40
    numx[4] = 50

    for _, j := range numx {
        fmt.Println("Value:", j)
    }   

}

Output:

Value: 10
Value: 20
Value: 30
Value: 40
Value: 50

Slice

Slice is a dynamic view of an array. Slice donΓÇÖt store anything by themselves, they reference an array. If we change something via the slice, the array is modified.

Creating slice :

// var VARIABLE_NAME []TYPE
var nameLists []string

// var VARIABLE_NAME = []TYPE{value1, value2, ...value_N}   
var numbers  = []int{10, 20, 30}   

// VARIABLE_NAME := []TYPE{value1, value2, ...value_N}   
fnumbers := []float32{10.23, 300.023}

Example:

package main

import "fmt"  

func main() { 
    var nameLists []string
    nameLists = append(nameLists, "John")
    nameLists = append(nameLists, "Mike")
    nameLists = append(nameLists, "Sammy")
    fmt.Println(nameLists)

    var numbers  = []int{10, 20, 30}
    numbers = append(numbers, 40)
    numbers = append(numbers, 50)
    fmt.Println(numbers)

    fnumbers  := []float32{10.23, 300.023}
    fnumbers = append(fnumbers, 12.23)
    fnumbers = append(fnumbers, 800.23)
    fmt.Println(fnumbers)
}

Loops in Go

There is only for loop is available in Go. Syntax :

for [condition | (initialization; condition; increment) | range ]
{
    statement(s)
}

Example :

package main 

import "fmt"

func main() {
    message := "Hello World"

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

Using Ranges : Ranges are used to iterate through variables such as array, slice and maps. Range basically returns two values index and value. Syntax :

for var_1, var2 := range Array_Name {
    fmt.println("Index :", var_1, "| value:", var_2)
}

Where var_1 contains index of element and var_2 contains element value. Example :

package main

import "fmt"

func main() {
    numx := []int{10,20,30,40,50}  

    for i, j := range numx {
        fmt.Println("Index:", i, "| Value:", j)
    }   

}

Output :

Index: 0 | Value: 10
Index: 1 | Value: 20
Index: 2 | Value: 30
Index: 3 | Value: 40
Index: 4 | Value: 50

To print just values with range we can provide an empty var _ in place of index

for _, var2 := range Array_Name {
    fmt.println("Index :", var_1, "| value:", var_2)
}

Example :

package main

import "fmt"

func main() {
    numx := []int{10,20,30,40,50}

    for _, j := range numx {
        fmt.Println("Value:", j)
    }

}

Output:

Value: 10
Value: 20
Value: 30
Value: 40
Value: 50

Conditional Statement

if statement :

if condition {
    statment(s)
}

if-else statement :

if condition {
    statment(s)
} else {
    statment(s)
}

if-else-if statement :

if condition {
    statment(s)
} else if {
    statment(s)
}

Example:

package main

import "fmt"

func main() {
    var age int
    fmt.Printf("Enter your age: ")
    fmt.Scan(&age)
    if age < 18 {
        fmt.Println("You are a miner")
    } else if age >= 18 && age < 40 {
        fmt.Println("You are an Adult")
    } else if age > 40 {
       fmt.Println("You are an Adult but in 40s or so, Take care of your Health!!")
    }
}

Switch Case :

Syntax :

switch expression {
    case x:
        // code statement
    case y:
        // code statement
    case z:
        // code statement
    default:
        // code statement
}

Example :

package main

import "fmt"

func main() {
    var ch string

    fmt.Printf("Enter your choice [Y|N]: ")
    fmt.Scan(&ch)

    switch ch {
        case "N":
            fmt.Println("You selected No")
        case "Y":
            fmt.Println("You selected Yes")
        default:
            fmt.Println("Did not understand your choice!!")
    }
}

Output

$ go run main.go
Enter your choice [Y|N]: Y
You selected Yes

$ go run main2.go
Enter your choice [Y|N]: y
Did not understand your choice!!

Another example:

package main

import "fmt"

func main() {
    var ch string

    fmt.Printf("Enter your choice [Y|N]: ")
    fmt.Scan(&ch)

    switch ch {
        case "N":
        case "n":
            fmt.Println("You selected No")
        case "Y":
        case "y":
            fmt.Println("You selected Yes")
        default:
            fmt.Println("Did not understand your choice!!")
    }
}

Output:

$ go run main.go
Enter your choice [Y|N]: y
You selected Yes

$ go run main.go
Enter your choice [Y|N]: n
You selected No

Functions

Function syntax in Go

func function_name([parameter list]) [return_types] {

    statements....
}
  • function_name : function should be named in camelCase.
  • Arguments : A function can take zero or more arguments. For two or more consecutive arguments with the same type, you can define the type on the back of the last argument.
  • Return Types : A function can return zero or more values. If returns more than one values, you need to cover them with parentheses.

Example 1 :

func testFunc() string {
    return "Hello World"
}

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

Example 2 :

func testFunc(x string) string {
    return "Hello "+x
}

func main() {
    fmt.Println(testFunc("Ajay"))
}

Example 3 :

func testFunc(x string) (msg string) {
    msg = "Hello " + x
    return 
}  

func main() {
    fmt.Println(testFunc("Ajay"))
}

Example 4 :

func testFunc(x, y string, z int) (string, string, int) {
    a := "First String" + " " + x
    b := "Second String" + " " + y
    c := 20+z
    return a, b, c
}  

func main() {
    str1, str2, num := testFunc("Hello", "World", 10)

    fmt.Println(str1)
    fmt.Println(str2)
    fmt.Println(num)
}
  • Exported/Unexported Functions : Functions can be exported if first letter of their name is capitalized and unexported if first letter of function name is small.
  • Anonymous Function : Anonymous function can be declared inside a function and execute immediately after decaling it. The anonymous function can access the scope of its parents.
package main  

import "fmt"  

func main() {

    myFunc := func(x int) int {
        return x*x
    }

    a := myFunc(3)
    b := myFunc(6)
    c := myFunc(9)

    fmt.Printf("a: %v\nb: %v\nc: %v\n", a, b, c)
}

Structure

structure is a user-defined datatype which contains one or more element of the same or different type.

Defining a structure :

type structureName struct {
    variable_1 variable_type
    variable_2 variable_type
    ...
    variable_3 variable_type
}

Declaring a variable :

Once a structure type is defined, it can be used to declare variables of that type using following syntax :

var variable_name structureName

Example :

package main 

import "fmt"  

// defining structure 
type details struct {
    name string
    age int
    occup string
}

func main() {

    // declaring structure variable 
    var std1 details 

    // assigning values 
    std1.name = "John snow"
    std1.age = 20
    std1.occup = "Student"

    // printing values 
    fmt.Println("Name:", std1.name)
    fmt.Println("Age:", std1.age)
    fmt.Println("Occupation:", std1.occup)
}

We can also create a variable and assign values to it :

variableName := structureNam{"Value1", "Value2", ..."Value_N"}

Example :

package main 

import "fmt"  

// defining structure 
type details struct {
    name string
    age int
    occup string
}

func main() {

    // declaring structure variable 
    // and assigning values 
    std1 := details{"John snow",20, "Student"}

    // printing values 
    fmt.Println("Name:", std1.name)
    fmt.Println("Age:", std1.age)
    fmt.Println("Occupation:", std1.occup)
}

Struct can be also instantiate using the new keyword as well as using pointer address operators.

package main 

import "fmt"  

// defining structure 
type details struct {
    name string
    age int
    occup string
}

func main() {

    // initiate struct using new keyword  
    std1 := new(details)

    std1.name = "John snow"
    std1.age = 20 
    std1.occup = "Student"

    // printing values 
    fmt.Println("Name:", std1.name)
    fmt.Println("Age:", std1.age)
    fmt.Println("Occupation:", std1.occup)

    // initiate struct using pointer & operator 
    var std2 = &details{"John Doe", 30, "IT Administrator"}

    // printing values 
    fmt.Println("Name:", std2.name)
    fmt.Println("Age:", std2.age)
    fmt.Println("Occupation:", std2.occup)
}

Go Methods :

Methods are special type of functions which works on the variable of a certain type called receiver. A receiver is usually a struct or an alias type or a pointer pointing to an instance of a struct or an alias.

The struct and sets of methods (called method-sets) can be used to mimic object-oriented programming equivalent of class. The receiver is the part of the method, so two methods of the same name can exist on different types.

Syntax:

func (recv receiver_type) methodName(parameter_list) (return_value_list)  {
    statement(s)
}

Example 1:

package main 

import "fmt"  

// defining structure 
type details struct {
    name string
    age int
    occup string
}

func (dt details) Display() {
    fmt.Println("Name:", dt.name)
    fmt.Println("Age:", dt.age)
    fmt.Println("Occupation:", dt.occup)
}

func main() { 
    st1 := details{"John Doe", 30, "IT Administrator"}
    st1.Display()
}

Example 2:

package main 

import "fmt"  

// defining structure 
type specs struct {
    height int
    width int
    depth int
}

func (dt specs) Calculate() int {
    return dt.height*dt.width*dt.depth
}

func main() { 
    st1 := specs{10, 20, 30}
    fmt.Println(st1.Calculate())
}

Pointers

Go-Lang supports pointers. Pointer declaration

var pointer_name *var-type

or

var pointer_name = &variable

Assigning address of a variable to pointer variable using & operator, similar to C language

var value = 10
var *ptr int
ptr = &value

Now by using *pointer_name the value is accessed and by using just the pointer name only address can be accessed. Example

package main 

import "fmt"  

func main() { 
    var value = 20
    var num_p *int
    num_p = &value
    fmt.Println("Value:", *num_p)
    fmt.Println("Addess:", num_p)
    fmt.Printf("%T\n\n", num_p)

    name := "Don Joe"
    var ptr = &name
    fmt.Println("Value:", *ptr)
    fmt.Println("Addess:", ptr)
    fmt.Printf("%T\n", ptr)
}

Output:

Value: 20
Addess: 0xc0000b8000
*int

Value: Don Joe
Addess: 0xc00009e210
*string

Note :

  • Go does not support pointer arithmetic.
  • Functions/methods accept both variables and pointers.
  • Pass pointers when function/method needs to modify the parameter.
  • When a variable is passed, the function/method gets a copy and the original copy is not modified. With pointers the underlying value is modified.

Command Line Arguments

Command line arguments can be accessed using os packages.

os.Args[INDEX]

where index starts from 0. Code example :

package main

import (
    "fmt"
    "os"
)

func main() {
    ArgsLen := len(os.Args)
    fmt.Println("Number of Length:", ArgsLen)
    for i:=0; i<ArgsLen;i++ {
        fmt.Printf("%v Argument: %v\n", i+1,os.Args[i])
    }   
}

Output:

$ go run main1.go 
Number of Length: 1
1 Argument: /tmp/go-build2920941665/b001/exe/main1

go run main1.go Hello world
Number of Length: 3
1 Argument: /tmp/go-build1528893215/b001/exe/main1
2 Argument: Hello
3 Argument: world

With compiled binary

$ go build main1.go 
$ ./main1 Hello world
Number of Length: 3
1 Argument: ./main1
2 Argument: Hello
3 Argument: world

Concurrency in Go

  • Concurrency is implemented in golang using Goroutines.
  • A Goroutine is a function or method which executes independently and simultaneously in connection with any other Goroutines present in your program.
  • Some important points are :
    • Goroutines are like light-weighted thread but the cost or resources needed for goroutines is very small as compared to the thread.
    • Every program contains at least a single Goroutine and that Goroutine is known as the main Goroutine.
    • All the Goroutines are working under the main Goroutines if the main Goroutine terminated, then all the goroutine present in the program also terminated.
    • Goroutine always works in the background.

Creating Goroutine

A goroutine is created by using a keyword go followed by a function name. Example :

package main 

import (
    "fmt"
    "time"
)

func printT(tNo int, t int, delay int) {
    for i:=0;i<t;i++ {
        fmt.Println("ThreadNo:", tNo, "| Delay:", delay, "seconds | Counter:",i)
        time.Sleep(time.Second * time.Duration(delay))
    }
}

func main() {    
    go printT(1, 5, 3)    
    go printT(2, 6, 2)    
    go printT(3, 7, 1)    
    var str string
    fmt.Scanln(&str)
}

As we can see that the function printT is called using go routines.

GO routines with anonymous functions.

package main

import (
    "fmt"
    "time"
)

func main() {

    go func() {
        for i:=0;i<6;i++{
            fmt.Println("01: Hello world")
            time.Sleep(time.Second*1)    
        }
    }()    


    go func() {
        for i:=0;i<3;i++{
            fmt.Println("02: Hello Test")
            time.Sleep(time.Second*2)    
        }
    }()    

    var str string
    fmt.Scanln(&str)
}

Channels

Channels enables the communication between Goroutines, which allows them to synchronize their execution according to the requirements. channel (chan) is a go datatype which basically pass data between goroutines.

Channel variable is declared using

var c chan Datatype = make(chan Datatype)

Example :

var c chan string = make(chan string)

// or

c := make(chan string)
var c chan int = make(chan int)

// or

c := make(chan int)

Now the above variable will be passed to each thread and then the data will be send and received using these variables. An Example program

package main

import (
    "fmt"
    "time"
)

func SayHello(c chan string) {
    for i := 0;;i++ {
        c <- "Hello world"
    }
}

zgfunc printMsg(c chan string) {
    for {
        msg := <- c
        fmt.Println(msg)
        time.Sleep(time.Second*1)
    }
}

func main() {
    var c chan string = make(chan string)
    // or you can use 'c := make(chan string)'
    go SayHello(c)
    go printMsg(c)

    var str string
    fmt.Scanln(&str)
}
  • The above program is an example of a goroutine channel, where channel variable c is declared, then it is passed to both goroutine functions.
  • Now when SayHello function execute it will send string data ΓÇ£Hello worldΓÇ¥ into the channel via channel variable c, and we can see in another goroutine function printMsg the channel variable c store the channel data into the string variable msg and the message gets printed in next line.
  • And after 1 second of delay it will receive next message through channel.
  • Also note that this channel is synchronized, means for 1 second delay the function SayHello have to wait and after that it will resume in its execution.

Channel Direction

In golang channels are by-default bi-directional, but you can also create unidirectional channels. For example to send data to the channel

c chan<- string

To receive data to the channel

c <-chan string

Example Program :

package main   

import (
    "fmt" 
    "time"
)

// Only send data to channel 
func SayHello(c chan<- string) {
    for i := 0;;i++ {
        c <- "Hello world"
    }
}

// Only receive data to channel 
func printMsg(c <-chan string) {
    for {
        msg := <- c
        fmt.Println(msg)
        time.Sleep(time.Second*1)
    }
}

func main() {
    var c chan string = make(chan string) 
    // or you can use 'c := make(chan string)'
    go SayHello(c)
    go printMsg(c)

    var str string
    fmt.Scanln(&str)
}

Another example of channels

package main

import (
    "fmt"
)

func SetMsg(ch1 chan<- string) {
// defining ch1 to send data
    ch1 <- "Hello world"
}

func GetMsg(ch1 <-chan string, ch2 chan<- string) {
// defining ch1 to receive data and ch2 to send data
    msg := <- ch1
    ch2 <- msg
}

func main() {
    // declaring channels with buffered capacity of 1
    ch1 := make(chan string, 1)
    ch2 := make(chan string, 1)

    SetMsg(ch1)
    GetMsg(ch1,ch2)
    fmt.Println(<-ch2)
}

Select

Select statement lets user wait for multiple channel operations. It basically works like switch statement for channels. Example

package main 

import (
    "fmt"
    "time" 
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func(){
        for{
            ch1 <- "Message from Func1"
            time.Sleep(1*time.Second)
        }
    }()


    go func(){
        for{
            ch2 <- "Message from Func2"
            time.Sleep(2*time.Second)
        }
    }()

    for { 
        select {
            case msg1 := <- ch1:
                fmt.Println(msg1)
            case msg2 := <- ch2:
                fmt.Println(msg2)
        }
    }
}

we can also implement timeout option with

for {
    select {
        case msg1 := <- ch1:
            fmt.Println(msg1)
        case msg2 := <- ch2:
            fmt.Println(msg2)
        case <- time.After(2*time.Second):
            fmt.Println("No messages received: Time out")
    }
}

There is also a default option is available in select, but it keep invoked continuously,

for {
    select {
        case msg1 := <- ch1:
            fmt.Println(msg1)
        case msg2 := <- ch2:
            fmt.Println(msg2)
        default:
            fmt.Println("No data received")
    }
}

Buffered Channels

  • By-default channels are unbuffered, means they only accepts sends (chan <-) if there are corresponding receive (<- chan), which are ready to receive the send values, otherwise channel will be blocked.
  • To circumvent that, buffered channels can be used, it will allows to accept a limited number of values without a corresponding receiver for those values.
  • Buffered channel are blocked only when the buffer is full.
  • Similarly receiving from a buffered channel are blocked only when the buffer will be empty.

Creating Buffered channel

ch := make(chan_type, capacity)

The capacity of buffered channel will be greater then 0, because unbuffered channel has capacity of 0.

Example :

package main 

import (
    "fmt"
)

func main() {
    // channel with capacity 2 
    ch := make(chan string, 2)

    ch <- "Message 1"
    ch <- "Message 2"

    // receiving/priting both messages
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

Example 2 :

package main

import (
    "fmt"
    "time"
)

func SetMessage(ch chan<- string, msg string) {
    for i:=0;i<5;i++ {
        ch <- msg
    }
}

func main() {
    ch := make(chan string, 10)

    go SetMessage(ch, "Message One")
    go SetMessage(ch, "Message Two")

    // wait for 5 second
    time.Sleep(5*time.Second)
    // close the channel
    close(ch)

    for v := range ch {
        fmt.Println(v)
        time.Sleep(100*time.Millisecond)
    }
}

Example 3 :

package main

import (
    "fmt"
    "time"
)

func AddMsg(ch chan<- string, msg string) {
    for i:=0;i<5;i++ {
        ch <- msg
    }   
    close(ch)
}

func main() {
    ch := make(chan string, 5)   
    go AddMsg(ch, "Hello World")
    time.Sleep(2*time.Second) 
    for msg :=  range ch {
        fmt.Println(msg)
    } 
}

Using sync module to wait for goroutines

First create a waitgroup variable

var wg sync.WaitGroup

Then add number of goroutines you want to wait for

wg.Add(2)
  • Now provide each goroutines with wg pointer and after ending of each goroutine set wg.Done() which basically decrements the number of goroutines (which is in this case 2).
  • Now on main function put wg.Wait() to wait for all the goroutines.
  • Now look the example project.
package main

import (
    "fmt"
    "time"
    "sync"
)

func Print(wg *sync.WaitGroup, tm int, msg string) {
    time.Sleep(time.Duration(tm) * time.Millisecond)
    fmt.Println(msg)
    wg.Done()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(3)
    go Print(&wg, 1000, "Hello world From 1")
    go Print(&wg, 2000, "Hello world From 2")
    go Print(&wg, 3000, "Hello world From 3")
    wg.Wait()
    fmt.Println("All routines completed")
}

Interfaces in Go

Interfaces are named collections of method signatures as well as custom type.

Maps

Map is similar to hashes or dictionary. Creating an empty map :

var mymap = make(map[key-type]int)

Example :

var mymap = make(map[string]int)

Example :

package main

import "fmt"

func main() {
    mymap := make(map[string]int)
    mymap["num1"] = 200
    mymap["num2"] = 210
    mymap["num3"] = 220
    mymap["num4"] = 230

    // print all maps
    fmt.Println("Print all values : ")
    fmt.Println(mymap)

    // print key values
    fmt.Println("\nPrint only keys:values pair : ")
    for key, val := range mymap {
        fmt.Println(key, "=>", val)
    }

    // print only keys
    fmt.Println("\nPrint only keys : ")
    for key,_ := range mymap {
        fmt.Println(key)
    }

    // print only values
    fmt.Println("\nPrint only values : ")
    for _,val := range mymap {
        fmt.Println(val)
    }
}

Output :

Print all values :
map[num1:200 num2:210 num3:220 num4:230]

Print only keys:values pair :
num1 => 200
num2 => 210
num3 => 220
num4 => 230

Print only keys :
num1
num2
num3
num4

Print only values :
200
210
220
230

Errors in GoLang

Most functions and methods returns at-least one error value (or nil), so we can use that to show errors. Example :

package main

import (
    "fmt"
    "os"
)

func main() {
    myfile, err := os.Open("file.txt")
    if err != nil {
        fmt.Println("[!] Error : ", err)
        return
    }
}

Output :

[!] Error :  open file.txt: no such file or directory

It can also be implemented like this

func main() {
    if _, err := os.Open("file.txt"); err != nil {
        fmt.Println("[!] Error : ", err)
        return
    }
}

Using fmt.Errorf

package main

import (
    "fmt"
    "math"
)

func Sqrt(x float64) (float64, error) {

    if x < 0 {
        return 0, fmt.Errorf("cannot Sqrt negative number: %v", float64(x))
    }

    return math.Sqrt(x), nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

Output :

1.4142135623730951 <nil>
0 cannot Sqrt negative number: -2

As we can see the Sqrt return 2 values. We can use error check to get only values.

package main

import (
    "fmt"
    "math"
)

func Sqrt(x float64) (float64, error) {

    if x < 0 {
        return 0, fmt.Errorf("cannot Sqrt negative number: %v", float64(x))
    }

    return math.Sqrt(x), nil
}

func main() {
    res1, err := Sqrt(2)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(res1)
    }

    res2, err := Sqrt(-2)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(res2)
    }
}

Output :

1.4142135623730951
cannot Sqrt negative number: -2

We can also create a function to handle error instead of checking it each time with function call

package main

import (
    "fmt"
    "math"
)

func checkError(err error) {
    if err != nil {
        fmt.Println(err)
    }

}

func Sqrt(x float64) (float64, error) {

    if x < 0 {
        return 0, fmt.Errorf("cannot Sqrt negative number: %v", float64(x))
    }

    return math.Sqrt(x), nil
}

func main() {
    res1, err := Sqrt(2)
    checkError(err)
    if res1 != 0 {fmt.Println(res1)}

    res2, err := Sqrt(-2)
    checkError(err)
    if res2 != 0 {fmt.Println(res1)}
}

Output :

1.4142135623730951
cannot Sqrt negative number: -2

References:

Reading/writing to a file

0
Subscribe to my newsletter

Read articles from Ajay Tekam directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ajay Tekam
Ajay Tekam

I am working as a Cloud Engineer with experience in DevOps, automation, CICD, build pipelines, jenkins pipelines, version control, shell scripting, python automation, golang automation, cloud services (AWS, OCI, Azure), containers and microservices, terraform, ansible.