Introduction To Go

Go/Golang is developed and supported by Google to obtain high-performance by having the static typing just like C, alongwith introducing simpler way for concurrent programming to achieve high bandwith throughput. As a result Golang became easier to grasp for a newcomer, and the standard libraries took care of basic http request-response model. More than 80% of codebase for Docker and kubernetes written in Go which popularized it, and Go became the de-facto choice for developing services and products on top of Docker and Kubernetes.

The main objective here is to provide an overview of Golang an snippet to understand the basics

Array

Example 01: Intro to Array
package main

import ("fmt";"math/rand"; "time";"os")

func main2(){


	const(
		winter = 1
		summer = 3
		yearly = summer + winter
	)


	// var books [4]string
	
	books := [4]string{
		"book1",
		"book2",
		"book3",
	}

	// i := 4
	// books[i] = "asd"

	// books[4] = "asd"

	fmt.Printf("%T\n",books)
	fmt.Println(books);

	for i:=0;i<len(books);i++ {
		fmt.Println("Book element at index ",i,":",books[i])
	}

	fmt.Println("Unitialized string literal is empty string:", books[3]=="")
}
Example 02: Array with length determined at compile time
// import "math/rand"

func main3(){
	moods := [...]string {"awesome","good","okayish","bad","terrible"}

	if len(os.Args) <2 {
		fmt.Println("[your name]")
		return
	}
	var name string = os.Args[1]
	rand.Seed(time.Now().UnixNano())
	var index int = rand.Intn(len(moods))
	fmt.Println(name," is currently feeling ",moods[index],"!")
}
Example 03: Array variable type checking includes the length also
func main4(){
	books1 := [4]string {"asd","asd","asd"}
	books2 := [4]string {"asd","asd","asd"}
	books3 := books1
	books4 := [4]string {"asd","asd","asd2"}
	books5 := [3]string {"asd","asd","asd2"}
	fmt.Println("Compare books1 & books2: ",books1==books2)
	fmt.Println("Compare books2 & books3: ",books2==books3)
	fmt.Println("Compare books1 & books4: ",books1==books4)
	// fmt.Println("Compare books1 & books5: ",books1==books5) // not compable: type is different.. compile time error

	fmt.Println(books5)
	// question: how to get type of variables?
}
Example 04: Array variable type checking includes type of array aling with length of array
func main5(){
	books1 := [4]string {"book1","book2","book3"}
	books2 := books1
	books3 := [3]string {"book1","book2","book3"}

	books1[0] = "book_mod"
	fmt.Println("Books1 elements:",books1)
	fmt.Println("Books2 elements:",books2)
	fmt.Println("Books3 elements:",books3)

	// books2 = books3 // Type mismatch, hence assignment wont work here
}
Example 05: Iterations of array elements
func main6(){
	array := [2][3] int{
		{1,2,3},
		{4,5},
	}

	for i:=0;i<len(array);i++{
		for j:=0;j<len(array[i]);j++ {
			fmt.Println("array at index ",i,",",j,": ",array[i][j])
		}
	}

	fmt.Println(array)
}
Example 06: Array type checking doesn’t consider underlying type unless one of them is underlying type
func main(){
	type booktype [3]int
	type cabinet [3]int
	books1 := booktype{3,4,5}
	books2 := [3]int {3,4,5}
	books3 := cabinet{3,4,5}

	fmt.Println("Compare books1 & books2: ",books1==books2)
	fmt.Println("Compare books3 & books2: ",books3==books2) // unnamed and named type comparision is resolved to underlying type
	// fmt.Println("Compare books1 & books3: ",books1==books3) // diff named types means different type, 
	fmt.Println("Compare books1 & books3: ",books1==booktype(books3))

	gadgets := [3]string{"Confused Drone"}
    fmt.Printf("%q\n", gadgets)
}

// consts are resolved compile time, no storage space allocated unlike variables
// check constants with iota

Function

Example 01: Basics of functions: Golang doesn’t support method overloading
package main

import "fmt"

func main(){
	fmt.Println("hello!")
	fmt.Println(fun1())
	fmt.Println("fun2() returns:", fun2())
	fmt.Println("fun3()  returns:", fun3(10))

	slice := []string{"hi!","there"}
	fmt.Println("slice: ",slice)
	fun4(slice)
	fmt.Println("slice: ",slice)

	array := [4]string{"hi!","there"}
	fmt.Println("array: ",array)
	fun5(array)
	fmt.Println("array: ",array)
}


func fun1() (string,string){
	// return "fun1 function" // always need to return same no. values declared
	return "fun1", "function"
}

// method overloading not supported in go
// func fun1(i int){

// }
Example 02: Named result value: Return mentions the return variable also: Return keyword implicitly declares, but needed
func fun2() (m string){
	return // return is required even for named result value to be returned automatically
}
Example 03: Return
func fun3(a int)(int){
	a++
	// return a++ //a++ means expecting expression
	return a
}
Example 04: Slice is a different data structure than array, which is why changes are reflected outside func4 but not in func5
func fun4(slice []string){
	slice[0] = "hello"
}

func fun5(array [4]string){
	array[0] = "hello"
}

Slice: dynamic array

Example 01: Slice can be compared only to nil, unlike struct and array
package main

import "fmt"

func main1(){
	var slice1 []int

	fmt.Println("slice1: ",slice1)

	var slice2 []int
	// fmt.Println("compare slice1 & slice2: ",slice1==slice2) //slice can only be compared to nil
	fmt.Println("Is slice1 empty: ",slice1==nil)
	fmt.Println("Length of slice2: ",len(slice2))

	slice3 := []string {"elem1","elem2","elem3"}
	fmt.Printf("Type of slice3: %T\n",slice3)
	fmt.Println("Length of slice3: ",len(slice3))
	fmt.Println("Is slice3 empty: ",slice3==nil)


	slice4 := append(slice3, "new_element")
	slice3[0] = "elem1_mod"
	fmt.Println("slice3 elements: ",slice3)
	fmt.Println("slice4 elements: ",slice4)

	// slice5 := append(slice3, slice4) // append cant append another slice, unpack slice to append all elements
	// fmt.Println("slice5 elemnets: ",slice5)
}
Example 02:

A slice is a descriptor of an array segment. It consists of a pointer to the array, the length of the segment, and its capacity (the maximum length of the segment).

func main4(){
	// Slicing uses same backing array
	nums := []int {1,2,3,4,5,6,7,8,9,0}
	slice := nums[0:5]

	fmt.Printf("Type of nums: %T\n",nums)
	fmt.Println("nums elements:",nums)
	fmt.Println("slice elements:",slice)

	slice[0] = 11
	nums[4] = 44
	
	fmt.Println("nums elements:",nums)
	fmt.Println("slice elements:",slice)


	nums2 := [10]int {1,2,3,4,5,6}
	slice2 := nums2[0:5]
	fmt.Printf("Type of nums2: %T\n",nums2)
	fmt.Println("nums2 elements:",nums2)
	fmt.Println("slice2 elements:",slice2)

	slice2[0] = 11
	nums2[4] = 44
	
	fmt.Println("nums2 elements:",nums2)
	fmt.Println("slice2 elements:",slice2)

	// How to break this usage of same backing array? => bys using append
	var slice3 []int
	slice3 = append(slice3, nums[0:5]...)
	slice3[2] = 22
	fmt.Println("nums elements: ",nums)
	fmt.Println("slice3 elements: ",slice3)
}
Example 03: Runtime error
func main(){
	nums := []int{1,2,3,4,5,6,7}
	fmt.Println("nums elements:",nums)
	nums = nums[1:3]
	fmt.Println("nums elements:",nums)
	nums = nums[:6]
	fmt.Println("nums elements:",nums)
	// nums = nums[:7] // runtime error: out of range due to capcpity of slice
}

Struct

Example 01: Basics of struct
package main

import "fmt"

func main1(){

	type person struct{
		firstname,lastname string
		age int
	}

	adam := person{
		firstname: "Adam",
		lastname: "mada",
		age: 10,
	}
	fmt.Println("Person struct: ",adam)
	fmt.Println("Person struct values: ",adam.firstname,adam.lastname,adam.age)

	adam.age = 20
	fmt.Println(adam)
}
Example 02: Structs are compable only if they’re of same type
func main(){

	type person struct {
		firstname, lastname string
		age int
	}

	pers1 := person{firstname:"perspn",lastname:"person",age:10}
	pers2 := person{firstname:"perspn",lastname:"person",age:10}
	pers3 := person{firstname:"perspn",lastname:"person",age:20}

	fmt.Println("Check equality pers1 & pers2: ",pers1==pers2)
	fmt.Println("Check equality pers1 & pers3: ",pers1==pers3)

	type book struct {
		name, author string
	}
	book1 := book{name:"name",author:"name"}

	fmt.Println(book1)
	// fmt.Println("Check equality: ",pers1==book1) // mismatched type

	type person2 struct {
		firstname, lastname string
		age int
	}
	pers4 := person2{firstname:"perspn",lastname:"person",age:20}
	fmt.Println(pers4)
	// fmt.Println("Check equality: ",pers3==pers4) // mismatched type

	var per6 person
	fmt.Println(per6)

	fmt.Printf("%T",per6)
}

OOP wrapper

package main

import "fmt"

type book struct{
	name, author string
	price int
}

// Following is a function
func printBook(b book){
	fmt.Println("book details: ",b.name," author:",b.author," with price:",b.price)
}

// Following is a method: it's enticed to the type books
func (b *book) print(){
	fmt.Println("book details: ",b.name," author:",b.author," with price:",b.price)
	b.price = 200
}
// Use pointer reciever whenever value in large or need to update in the method

func main(){
	book1 := book{name:"book_name",author:"book_author",price:20}
	printBook(book1)
	book1.print()
	book1.print()

	(&book1).print()

	// print(book1) //NO: method is only accessible through type
	// book.print(book1)

	// var book2 book = nil
	// book2.print()
}

// Go doesn't have implements keyword: enticing interface methods would automatically statisfy interface criteria
// A type only statisfies interface when it's entricing all the interface methods
// type assertion: interface.(dynamic type)
// g,ok = p.(interface{discount(float64)}) 

// type switch: p.(type): case int/string/...

// interface of an interface?

// Go cant automatically take dynamic value's address from interface value: A non-pointer value is non-addressable, a copy

Doubt 01: Method receivers Value or pointer is implicitly decided

package main

import "fmt"

func main() {
	fmt.Println("hello!")
	// doubt_function_pass()
	doubt_method_pointer_wopointer()
}

func doubt_function_pass() {
	book := Book{"book title", "book author"}
	fmt.Printf("Main: Address of book var: %p Value: %s\n", &book, book)
	checkPointer(&book)
	checkValue(book)
}

type Book struct {
	Title  string
	Author string
}

func checkPointer(book *Book) {
	fmt.Printf("checkPointer: Address of book var: %p Value: %s\n", book, *book)
}

func checkValue(book Book) {
	fmt.Printf("checkValue: Address of book var: %p Value: %s\n", &book, book)
}

func doubt_method_pointer_wopointer() {
	book := Book{"book title", "book author"}

	book1 := book
	fmt.Println("Following operations performed on struct value")
	book1.printBookDetailsValue()
	fmt.Println("book value: ", book1)
	book1.printBookDetailsPtr()
	fmt.Println("book value: ", book1)

	book2 := book
	fmt.Println("Following operations performed on struct pointer")
	book_ptr := &book2
	book_ptr.printBookDetailsValue()
	fmt.Println("book value: ", book2)
	book_ptr.printBookDetailsPtr()
	fmt.Println("book value: ", book2)
}

func (book Book) printBookDetailsValue() {
	book.Title = "book1"
	fmt.Printf("printBookDetailsValue: type: %T\n", book)
	fmt.Println("printBookDetailsValue: value:", book)
}

func (book *Book) printBookDetailsPtr() {
	book.Title = "book1"
	fmt.Printf("printBookDetailsPtr: type: %T\n", book)
	fmt.Println("printBookDetailsPtr: value:", book)
}
Written on January 1, 2023