Go Language Beginner readme.#
Go is a statically and Strongly typed language and also its a compiled language unlike JS, Python(dynamically typed). It also supports concurrency model, It also supports type system out of the box(like typescript for javascript). Since it is compiled language it turns our code into machine code quickly which makes the application faster on the run time. It also has built in garbage collection.
Go is Compiled Language#
Unlike python, javascript etc Go is a compiled language, meaning it will compile the human readable code and converts into machine code where that output machine code alone can be transfered to other people or put in servers to execute. Unlike hpython or js, other people or servers dont need js runtime(node) or python runtime, since in go we are transferring the direct machine code the OS(platform specific) can able to execute directly.
Memory management in Go#
Python, javascript etc are managed by garbage collector(Automatic memory management), and the code(Garbage collector code) that needs to mange
this memory will be in the python or js runtime.
But languages like C, C++ these are manual memory management, and needs to handled manually in our program.
Go is also managed by garbage collector(Automatic memory management).
Now there arise a question, since go is a compiled language like C, C++ etc, where does the Garabge collector code will be present
The answer is In every Go program when we are compiling, the garbage collector code(Go Runtime) will also be compiled along with it
Example for Statically and strongly typed language
const str: string; const num: number; console.log(str + ":" + num) => this will work in typescript(only statically typed)
var str string; var num int; println(str + ":" + num) => will not work in go(since it is both statically and strong type)
To summarize:
- Fast.
- Statically type.
- Concurrency.
- type safety.
- Compiled Language.
Introduction to Go#
To start with the files that are ending .go is considered as go lang file, similar to package.json
go.mod file situated in the root of working directory is the mod file which has the module name.
eg:
module "MODULE_NAME
- This MODULE_NAME is used when this file is being used as an external module by some other go code.
- Similar to npm publish package
package is another important concept in go, any file with package main is kinda root file on that working directory
also we can have a main function which will be defaultly called when running the go run FILE_NAME/regexpath
command.
We can also use some inbuilt standard library functions like fmt for printing, getting some inputs from command line.
EG: go lang folder structure(From noob’s perspective :( )
$HOME/
go.mod => where it contains the module name of this folder(may or may not be used as external module by others)
filename.go => main go file
running this file => either we can use go run filename.go / go run .(from that folder, which can trigger the main function)
Some initial observations
- Package name can start with either main / working_directory_folder name
- Each package should have a single name
Eg:
golang
-> go.mod => module "golang/test"
-> initial.go => can have package has main / golang
NOTE: package nested can be imported here if it packaged has nested,
nested
-> nested.go => can have package has main / nested
Go Variables
- bool => boolean type
- string => string type
- int(int, int8, int16, int32, int64) => integer(positive/negative whole numbers) type with various bits
- uint(unit, unint8, unit16, unit32, unit64) => unsigned(positive whole numbers) integer type with various bits
- float(float32, float64) => fraction numbers with various bits
- complex => imaginary numbers with various bits(very rare)
- byte => alias for unit8(basically one byte = 8bits)
- rune => alias for int32
- Go variables are pass by value, not by reference
Unless any application needs extreme performance care, otherwise its ok to use the below variables
- int, uint, float64,complex128
Variables can also be typecasted for EG:
- age := 12; ageFloat := float64(age);
Go also supports const for immutable type of variables, constant variables doesn’t support :=syntax
Go Print Statement Go follows similar approach to C language of using printf and sprintf
- printf => prints the formatted string
- sprintf => returns the formatted string(will not print in stdoutput)
- %v, %s => replaces %v with argument value EG: printf("Hi %v is manikandan", "this")
- %d => interpolate for integer in decimal form
- %f => interpolate for float
- %.2f => interpolate to float with rounding 2.
TIP: Sprintf is similar to ``` in js
Structs#
Structs are a collection of type(basically a js object literal ;)
EG:
type student Struct { name string age int }
Structs can also be nested inside anothe Struct
- Embedded Structs: These are similar to & in type in typescript.
Interfaces#
Interfaces are similar to typescript except that the fact in Go, interfaces act as function signature
EG: type message interface { getMessage() string }
Whomever structs implemented this interface will automatically inherits this interface
slices#
Pass by value in Go (default behavior)#
In Go, everything is passed by value. Some types contain internal references, so copying them still refers to shared data.
Value copies#
- basic types: int, float, bool, string
- fixed size arrays
- structs
Value copies with some shared data#
- slices (slice header is copied; underlying array is shared)
- maps (map header is copied; underlying hash table is shared)
- channels (channel handle is copied; underlying runtime channel is shared)
EG for above
func print(sl []int) {
fmt.Println(sl)
}
func main() {
s1 := make([]int, 0)
s1 = append(s1, 1, 2, 3, 4, 5)
// above structure { ptr: {1, 2, 3, 4, 5}, len: 5, cap: 5}
print(s1)
// print will receive header with new len, cap, but same ptr from actual s1 { len: 5, cap:5, ptr: s1.ptr }
}
Concurrency#
Concurrency in go is carried by go routines, a special keyword called go attached as a prefix to any function.
Go uses something called routines which can spawn a millions of them which mapped to the os threads.
Go runtime maintains a pool of OS threads from its processor(GOMAXPROCS -> basically the number of cores),
if u print runtime.GOMAXPROCS it will just print your number of cores, and these goroutines basically maps to these available os threads and its
done and managed by go run time.
it follows a gmp model, where g -> goroutine and m -> machine(OS threads) and p -> processor(real cores)..
a million g can exist, where as more than real cores of os threads can exist, where as actual cores
lets say we have a goroutine g1, it attaches to m1 and it attaches to p1(consider we have 2core machine), now lets assume g1 is making a network/io call, now this g1 is blocked, instead of wasting p1 resource doing nothing go runtime detaches m1 and p1, so now p1 is free to be assigned other machine(os threads) m2 or m3 or m4 etc. note: still g1 is attached to m1, now whenever m1 gets unblocked it assigns to any of p1 or p2. btw even is p isnt a real processor, its just a mimic to the real hardware processor to do all these stuff, if p is not really capped to real processor, lets say goruntime is using 1000p in 4core system, then the actual kernel will do most of its time in context switching then the actual job itself.
func main() {
delayAdd := func (a, b int) {
time.Sleep(10 * time.second)
fmt.Println(a + b)
}
go delayAdd(1, 2)
}
In the above code if u see, calling the add function normally like delayAdd() would be executing the code in the same thread(main, this is another goroutine btw the main one)
calling add with go delayAdd() will basically executes in another routine(thread).
In the above code also if u noticed, am not returning anything from delayAdd, this is coz the data cannot be returned and transferred to another routine, since all are running
at different contexts, will cover later on how to transfer values to other routines and stuff.
Also if u run the above code, you wont see any print statements of the addition result, its coz we have a timer delay of 10 seconds inside delayAdd goroutine, by the time
even before the timer starts our main program(main goroutine) would have exited.
In order to solve the above problem, we need to synchronize between different goroutines, this is acheived by a package called sync
func main() {
var wg sync.Waitgroup
delayAdd := func (a, b int) {
defer wg.done()
time.Sleep(10 * time.second)
fmt.Println(a + b)
}
wg.Add(1)
go delayAdd(1, 2)
wg.Wait()
}
The above code will basically prints the add result, no matter how much seconds we are sleeping over there, this is done with the help of sync package that we are using, basically waitgroup is nothing but it waits untill all the goroutine are done with their job from the waitgroup perspective(will come to later part when explaining channels), wg.Add basically tells the waitGroup number of goroutine it needs to track Add basically increments the counter, Done basically decrements the counter, wg.Wait() will basically waits untill the waitGroup comes back to 0. now u know the gist
Channels#
Channels are the best way to communicate between goroutines to share data. to define a channel u should use a keyword called chan.
dataTransfer := make(chan string)
go func(){
dataTransfer<-"Mani" // this statement means write into the channel
}()
fmt.Println(<-dataTransfer) // this statement means reading the channel
// this is a blocking call, if no one writes to the dataTransfer channel, this line
// will be forever blocked untill someone writes or closes it
value, bool := <-dataTransfer // channel also returns two things, one is the original value, another one is
// the boolean, boolean false determines that the channel is closed and no one is there to write,
// true determines that the channel got read from the writer, this is used also in for range
for val := range dataTransfer {
fmt.Println(val)
} // this will be blocked forever if the writer didnt closed the channel
There are also called buffered channels, where u can writes buffered things into your channel, consider you are creating a buffered
channel of 5 ch make(chan string, 5), so u can write 5continous things to your channel, without blocking or waiting for the read.
func main(){
bufferStream := make(chan int, 5) // a channel with buffer 5
go func() {
fmt.Println("Sending 1")
bufferStream <- 1
fmt.Println("Sending 2")
bufferStream <- 2
fmt.Println("Sending 3")
bufferStream <- 3
fmt.Println("Sending 4")
bufferStream <- 4
fmt.Println("Sending 5")
bufferStream <- 5
fmt.Println("Sending 6")
bufferStream <- 6
fmt.Println("Sending 7")
bufferStream <- 7
}()
select {} // ignore this line for now
}
when u ran the above program, you will print statements from sending 1 to sending 6, this is coz our buffer was 5, so initially it sends data from 1 to 5 and then
it printed out sending 6 to stdout, and then when it reached bufferStream <- 6 line, it knows that buffer is full 5/5, so it waits for any reader to pop off the value from
the buffer