const values must be known at compile time, so we cannot declare const from a functions return value
const are also scoped, i.e if const name start with caps only then its exported out of package and, if declared inside function cannot be accessed in other functions.
four types of constants: int, string, character(int32), bool
Datatypes
Basic Types
Integers
ex: var num int64 = 189
Int64 (Signed) -> 64 bits => 8 bytes
Uint64 (Unsigned) -> 0 to 2^64 - 1 numbers can be stored
also, there are int8, int16, int32 and similarly for uint
unintptr, can hold pointer of any size
Float64 and Float32 for precision based operations
ex: var f float64 = 3.56
Complex64, Complex128
ex: var c complex64 = 9 + 3i
Byte
ex: var mychar byte := 'r'
same as uint8
value between 0 - 255
can represent ASCII values
Rune
ex: rPound := '£'
same as int32
used to represent Unicode points
Unicode points are superset of ASCII
Unicode points can assign a number to any character in existence
UTF-8 stores every Unicode Point either using 1, 2, 3 or 4 bytes
String
ex: var name string="Alice"
Slice of bytes, i.e array of bytes
string is usually represented in utf-8 format
therefore, according to the character type in string, the size varies between 1-4 bytes.
Boolean (true of false)
ex: var isGood bool = true
More types
Pointer
you can declare pointer with new keyword or with &
* is the dereferencing pointer, using * we can access and change value pointed by the pointer.
lot of different things can be done once we start coupling both * and &
default value of pointer is nil
1
2
3
4
5
6
7
8
a:=new(int)*a=10fmt.Printf("Address of int is %d and value is %d",a,*a)b:=2c:=&b*c=100fmt.Println("Address of b is %d and value of b is %d",c,*c)
Struct
is a collectin of data of different types
fields can be accessed and set using .
empty structs or partial structs can be created
while creating new variables from struct, you need not provide field names
, is required for seperation of fields while creating struct variables
It is possible create pointers to struct variables using & or new keyword
we can directly change field values using pointer
two ways to print structs, using fmt package or using encoding/json package.
struct tags: used to add meta data to struct, can be used while encoding/decoding into different forms
Nested structs are possible, mainStruct.nestedStruct.fieldName to access nested structs field
Only struct which starts with caps are exported, same for struct fields.
Struct equality:
struct will be only equal if fields and values are same
also, fields needs to compatible for equality to work
slice, map and func are not supported/compatible to check equality
typeemployeestruct{namestringageintsalaryint}//empty struct variable
a:=employee{}//non empty struct
b:=employee{name:"noname",age:90,salary:0}//without field names
c:=employee{"noname",90,0}//access field
fmt.Println("name is %s",b.name)//set field
b.name="myname"sp:=&b(*sp).name="anothername"sp.name="anothername"//also works
Array
0 index based arrays
Arrays are values in go, that means there is no concept of pointer pointing to first element of array
we can iterate array using normal for loop or for range loop.
Size is fixed in arrays
1
2
3
4
5
6
7
8
9
10
11
12
13
arr:=[size]{type}{a1,a2...an}arr2:=[...]int{2,3}//length is equal to no of elements
arr3:=[4]int{1,2}//rest of values are 0's i.e zero value of type
//access
arr2[0]//assign
arr[1]=3//multiple element access possible, returns a copy in range
arr[index:]arr[:index]arr[:]//copy of entire array
Slice
Unlike array, slice is like a struct which points to an underlying array.
Length of slice is no.of elements in slice.
Capacity of slice is the max capacity that internal array can hold.
make can also be used to create slice
new can also be used
1
2
3
4
5
6
7
8
9
10
11
12
//creation
s:=[]int{1,2}fmt.Println(len(s),cap(s))//creating slice from other slice or array
arr:=[4]int{1,2,3,4}sl:=arr[1:3]//slice from array, len=2, cap=3
sll:=sl[:]//slice from another slice
numbers:=make([]int,3,5)//create using make, the later params are length and capacity
multiDim:=[][]int{{1,2,3},{3,4,5}}
- to append elements to slice: `func append(slice []Type, elems ...Type) []Type`
- internal size of will increase as slice length exceeds capacity
- unlike structs, for slices copies are not created on assigning slice to other variable. i.e they are referenced
- use `copy` function to perform copy of slice `func copy(dst, src []Type) int`
- default zero value of slice is `nil`
Map
dictionary / hashmap in go
search, insert, retrieve, delete in O(1)
Apart from Slice, Map, Function other types can be used as keys in map
Any type can be used as values in map
declare a map using map[key_type]value_type
using make => make(map[string]int)
zero value of map is nil
map is reference type, so assigning to other variable refers to same map
maps are not safe for concurrent use, use locks for concurrent access.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// access
m[key]//assign/create/modify
m[key]=value//delete
delete(map,key)//check if key exists
val,ok:=m[key]len(m)//to get length of map
fork,vinm{//iterate over map
fmt.Println(k,v)}
//without iota
const(a=0b=1c=2)//with iota
const(a=iotabc)//skip value in iota
const(a=iota_bc)//iota operations
const(a=iotab=iota+4c=iota*4)
Functions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// signature
funcname_of_function(param1int,param2string)(int,int){a,b:=1,2returna,b}// if parameters are of same type, we can club them into one
funcname_of_function(param1,param2string)(int,int){a,b:=1,2returna,b}//named returns, useful when function returns error
funcreturnAandB()(a,bint){a,b=1,2return}
Functions are also type in Go and, Higher order functions are supported. So, we can..
return functions from other functions.
pass functions as parameter to other functions.
assign function to a variable(anonymous functions)
create function types(declare a type with function signature)
Go also supports function closures!
Consider, function b which lives inside another function a.
From below example, the inner function b can access the counter variable even though it got returned.
And the copy of counter vairable is unique to each function returned from a
// for initialization; condition; post_statement{}
fori:=0;i<5;i++{fmt.Prinlnt("yello!")}// emulate while loop
i:=0fori<5{fmt.Prinlnt("yello!")i++}// for range loop on array
arr:=[]int{1,2,3,4}//array
forindex,value:=rangearr{fmt.Println(index,value)}// on string: we should use range loops on strings as UTF-8 encoded
sample:="a£b"foridx,letter:=rangesample{fmt.Println(idx,letter)}// on map
funcmain(){m:=map[string]string{"name":"john","vehicle":"car",}forkey,value:=rangem{fmt.Println(key,value)fmt.Println(m[key])}}// on channels, example taken from "golangbyexample.com"
funcmain(){ch:=make(chanstring)gopushToChannel(ch)forval:=rangech{fmt.Println(val)}}funcpushToChannel(chchan<-string){ch<-"a"ch<-"b"ch<-"c"close(ch)}
- there are no shorthand if/else in go
- there are no ternary statements in go
- if with initialization statement, you can declare variables in if statement and those variables are scoped to that particular if block. You can even call function in initialization statements.
1
2
3
4
ifa:=6;a<10{fmt.Println("less than 10")}
switch statements
break statements are not required
fallthrough to also check more cases after a case match
example if you open a file, you must close it. But we often forget it!
in Go, you can defer the required things before main logic.
if we have multiple defers statements, they will run in reverse order at the end of function.
inline defers, anonymous functions which will run at end of function
defer itself is implemented internally with stack
defer function are executed even if the program panics
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
funcwrite()error{file,err:=os.Open("temp.txt")iferr!=nil{returnerr}deferfile.Close()// this will execute at the end of function, executed second(i.e after below defer)
deferfunc(){fmt.Println("closing file..")}// first defer to be executed
n,err:=file.WriteString("text.....")iferr!=nil{returnerr}fmt.Printf("Number of bytes written: %d",n)returnnil}
Interfaces
Interface is a type in Go which is a collection of method signatures
With this you can achieve duck typing in Go. i.e we check if some attributes and methods are present.
The main reason why we must use interfaces is because we can mock them and hence testing becomes easier.
A type imlicitly implements interface if it implements all methods in interface.
typeAnimalinterface{walk()eat()}typedogstruct{ageint}func(ddog)eat(){fmt.Println("dog eats")}func(ddog)walk(){fmt.Println("Dog walks")}func(llion)eat(){fmt.Println("Lion eats")}func(llion)walk(){fmt.Println("Lion walk")}funccallEat(aAnimal){a.eat()}funcmain(){varaAnimalfmt.Println(a)//nil interface => zero value
a=dog{age:10}a.eat()a.walk()callEat(a)a=lion{age:24}callEat(a)l,ok:=a.(lion)//determine type of interface
}
In the above example, dog imlements the Animal interface. We can have other types as well which may implment Animal interface.
We can assign any variable types which implement an interface to interface variable.
We can also pass any variable types which implement an interface to function accepting that interface.
Interfaces help in writing more modular and decoupled code. Functions which accept interfaces becomes very generic to use.
pointer reciever vs value reciever
if we use pointer as reciever then both variable and pointer to the variable of that type can be used while assigning to interface or while passing to a function which accepts an argument as that interface.
if we use pointer as reciever then the only pointer to the variable of that type can be used while assigning to that interface or while passing to a function that accepts an argument as that interface.
A type can implement multiple interfaces.
Zero value of interface is nil.
Nested interfaces / embedded interfaces are possible. Then type must implement all methods from main interface and nested interface.
It is also possible to have interface in struct. Whenever we create instance of that struct we must pass an object to interface field which implements all methods of that interface.
An empty interface has no methods , hence by default all types implement the empty interface.
Goroutines
lightweight threads with own execution context which can run concurrently with other goroutines. Concurency is achieved using goroutines in Go.
Running goroutine is simple as just adding go keyword before invoking any function
Goroutine functions are run asynchronously, so the next statements execute immediately and does not wait for goroutine to complete execution.
Main function - is the main goroutine, which spawns other goroutines.
when main goroutine exits, program also exits and other goroutines may not even get executed.