Reference - Golang
Published on
GO env variables:
These are some go env variables
- GOROOT
- Location of GO SDK
export GOROOT=~/Downloads/go
export PATH=$PATH:$GOROOT/bin
-
GOPATH (for go version greater than 1.13) i.e using with GO modules
- downloaded source code is stored in $GOPATH/pkg/mod
- compliled binaries are stored in $GOPATH/bin
- we can change this behaviour with $GOBIN
- defaults to $HOME/go
-
GOBIN
- this is where all compiled binaries will be stored
- if we set this, we can run binaries without specifying full path
Run go env
to list all go related environment variables
Go commands
go mod init github.com/RahilRehan/banco
, Creates a go.mod filego install
, compiles and place binary in $GOBINgo build
, compiles and places binary in current directorygo run main.go
, compiles and executes the binarygo run cmd/*.go
, if main package contains multiple files
Go packages
- go packages is way to organize code and also helps in code reusability
- every go file starts with
package package_name
- ex:
package main
- ex:
- all go files with same package name are placed in same directory
Dependency management in Go
-
collection of packages(nested pacakages may exist) forms a Go module
-
there are go.mod and go.sum files is present in modules root
-
go.mod contains modules import path and list of dependencies
- if a direct dependency does not have go.mod internally, its dependencies are imported as indirect dependencies in projects go.mod file.
-
go.sum contains
- list of checksum of all direct and indirect dependencies with versions.
- this checksum is used to validate none of the dependencies got modified.
-
alias:
- Import packages with different names:
import config demo-app/repo/configuration
- Blank import is used to just run init function of package, ex:
import _ sql/pq
- Import packages with different names:
-
init function:
- Used to initialize global variables of a package.
- Executed when the package is initialized.
-
-
go get package_name
- downloads a dependency to current project, and adds it to go.mod -
go mod tidy
- makes sure the current projects dependencies reflect go.mod file and vice versa -
go mod download
- downloads dependencies listed in go.mod -
go mod vendor
- bundles all dependencies in vendor folder, reduces downloading dependencies -
import paths:
- import pacakge within same module:
import module_name/package_name
- in go.mod file use
replace package_name => new_package_name
to replace a package.(useful)
- import pacakge within same module:
-
semantic imports
- semantic versionning: V.{major}.{minor}.{patch}
- Go automatically picks up latest from minor and patch versions
- For using particular major version in imports, append major version to end of dependency , ex:
import "github.com/javascript/v8"
-
Notes
- semi colon is not requried at the end of statements in go
Variables
- Using var keyword(very explicit:
var variable_name type
, ex:var name string
.- All declared variables are assigned zero value of given type.
- Multiple variable declaration:
var one, two, three int64
- Declaraion + Assignment:
var one, two int64 = 1, 2
- declaration block with multiple types
var (
name string
age int64
vaccinated bool
)
- Type inferred declaration and initialization:
var a = 5
,var name = "Alice"
- type of the variable is automatically inferred from right hand side
- Short-hand declaration and initialization
a := 5
,name := "rahil"
- note that we can only use this type of declaration inside function
- for reassigning we cannot use
:=
,=
must be used
- Any unused variables in code will cause compilation error
- Local variables are one which are defined at function or block level
- Scope and lifetime of these variables until these blocks are in use
- Global variables are declared outside functions and blocks
- they are declared at package level
- If they start with capital alphabet, they are exported to other packages
- If they start with small alphabet, they are not exported to other packages
Constants
- Once declared, we cannot change the value
//single
const a string = "random"
const b = "inferred type"
//multiple
const (
E = "Exported"
n = 23
)
- 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
- ex:
- Float64 and Float32 for precision based operations
- ex:
var f float64 = 3.56
- ex:
- Complex64, Complex128
- ex:
var c complex64 = 9 + 3i
- ex:
- Byte
- ex:
var mychar byte := 'r'
- same as uint8
- value between 0 - 255
- can represent ASCII values
- ex:
- 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
- ex:
- 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.
- ex:
- Boolean (true of false)
- ex:
var isGood bool = true
- ex:
- Integers
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
- you can declare pointer with
a := new(int)
*a = 10
fmt.Printf("Address of int is %d and value is %d", a, *a)
b := 2
c := &b
*c = 100
fmt.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
&
ornew
keyword - we can directly change field values using pointer
- two ways to print structs, using
fmt
package or usingencoding/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
- new copy of struct is created when
- struct variable assigned to other variable
- struct variable passed into a function
- to save memory, use pointer whenever possible
type employee struct {
name string
age int
salary int
}
//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
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 slicenew
can also be used
//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.
// 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
for k, v in m{ //iterate over map
fmt.Println(k, v)
}
- IOTA
- similar to enums
//without iota
const (
a = 0
b = 1
c = 2
)
//with iota
const (
a = iota
b
c
)
//skip value in iota
const (
a = iota
_
b
c
)
//iota operations
const (
a = iota
b = iota + 4
c = iota * 4
)
Functions
// signature
func name_of_function(param1 int, param2 string) (int, int){
a, b := 1, 2
return a, b
}
// if parameters are of same type, we can club them into one
func name_of_function(param1, param2 string) (int, int){
a, b := 1, 2
return a, b
}
//named returns, useful when function returns error
func returnAandB()(a, b int){
a, b = 1, 2
return
}
-
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 functiona
. - From below example, the inner function
b
can access thecounter
variable even though it got returned. - And the copy of
counter
vairable is unique to each function returned froma
- This is called closure
- Consider, function
func a() (b func() int){
counter := 0
b = func () int{
counter += 1
return counter
}
return
}
func main() {
b := a()
fmt.Println(b())
fmt.Println(b())
c := a()
fmt.Println(c())
fmt.Println(c())
}
//output
1
2
1
2
- Immediate invocation is possible, i.e add
()
at the end of function definition to invoke it. - Variadic Parameters
func add(numbers ...int)
- pass any number of int params to add function
numbers
is an array of intsadd(1, 2, 3, 3, 4, 5, 6)
, this is how we can call it, and params can vary in length
Methods
- Attaching functions to types
func (r receiver_type) func_name(args) returns
- This will let us emulate object oriented behavior, where types can have functionality attached to them
- Very similar to calling methods on objects
- Using pointer receivers will decrease memory usage and we will also be able to change values from method.
- i.e we can emulate setters
- Reciever types must be defined in the same package
- If we have nested struct, we can directly call method attached to nested struct
- If method names begin with caps then they are exported out of package, but then type also must be public.
Loops
- there is no while loop in go
- there are only two types of loops,
- for loop
- for range loop
- can be used to iterate over array, slice, string, map and channels
- using range keywords return two values => index and value
- we can ignore second return by using a
_
or just using one variable on left hand side
break
andcontinue
statements work just as in any other programming languagelabels and goto
are also present in golang, to go to particular label from any point in program
// for initialization; condition; post_statement{}
for i:=0; i < 5; i++ {
fmt.Prinlnt("yello!")
}
// emulate while loop
i := 0
for i<5{
fmt.Prinlnt("yello!")
i++
}
// for range loop on array
arr := []int{1, 2, 3, 4} //array
for index, value := range arr{
fmt.Println(index, value)
}
// on string: we should use range loops on strings as UTF-8 encoded
sample := "a£b"
for idx, letter := range sample {
fmt.Println(idx, letter)
}
// on map
func main() {
m := map[string]string{
"name" : "john" ,
"vehicle" : "car",
}
for key, value := range m{
fmt.Println(key, value)
fmt.Println(m[key])
}
}
// on channels, example taken from "golangbyexample.com"
func main() {
ch := make(chan string)
go pushToChannel(ch)
for val := range ch {
fmt.Println(val)
}
}
func pushToChannel(ch chan<- string) {
ch <- "a"
ch <- "b"
ch <- "c"
close(ch)
}
Conditional statements
- if/else statements
if condition {
//Do something
} else if condition {
//Do something
} else {
//Do something
}
- 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.
if a:= 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
switch statement(pre); expression {
case expression1, expresstion2:
//Dosomething
fallthrough
case expression2:
//Dosomething
default:
//Dosomething
}
defer
- helps in deferring some activity
- used for cleanup activities
- 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
func write() error {
file, err := os.Open("temp.txt")
if err != nil {
return err
}
defer file.Close() // this will execute at the end of function, executed second(i.e after below defer)
defer func(){fmt.Println("closing file..")} // first defer to be executed
n, err := file.WriteString("text.....")
if err != nil {
return err
}
fmt.Printf("Number of bytes written: %d", n)
return nil
}
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.
type Animal interface {
walk()
eat()
}
type dog struct {
age int
}
func (d dog) eat() {
fmt.Println("dog eats")
}
func (d dog) walk() {
fmt.Println("Dog walks")
}
func (l lion) eat() {
fmt.Println("Lion eats")
}
func (l lion) walk() {
fmt.Println("Lion walk")
}
func callEat(a Animal){
a.eat()
}
func main() {
var a Animal
fmt.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.
//embedded interface
type ReadWriter interface {
Reader
Writer
}
- 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.
- Read more abour run queues in go here
//example
func threadSpawner(id int) {
fmt.Println(id)
}
func main() {
fmt.Println("Start")
for i := 0; i < 10; i++ {
go threadSpawner(i)
}
time.Sleep(time.Second * 2)
fmt.Println("Finish")
}
// anonymous goroutines
go func(){
//body
}(args..)
Channels
- Channels provide communications and synchrounization between goroutines.
- Channels internally manage locks as well.
- They act like pipes, you can perform only two actions. Send to channel, Recieve from channels.
func main() {
var ch chan int
ch = make(chan int)
//send into channel
ch <- 5
//recieve from channel
val := <- ch
}