What I liked while learning GO language?
- No need of Semi-Colons to end a statement.
- Compilation error if you haven’t used a declared variable.
- Functions are more powerful in Go.
Function declaration syntax:
func functionName(<input parameters>) (<return value types>){}
Example 1: Single return
func max(num1, num2 int) int {
return 80;
}
max function takes num1 and num2 integer parameters and returns an integer.
Example 2: Multiple return
func max2(num1 string, num2 int) (int,string) {
return 80,”hi”;
}
max2 function takes num2 integer and num1 string as input parameters and returns an integer and string.
Example 3:Named returns from function(Automatic return)
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum — x
return
}
split function will return x and y automatically.
4. Power of Switch Case
The Break statement that is needed at the end of each case in those languages is provided automatically in Go. Another important difference is that Go's switch cases need not be constants.
Example 1:
switch os := runtime.GOOS; os {
case “darwin”:
fmt.Println(“OS X.”)
case “linux”:
fmt.Println(“Linux.”)
default:
fmt.Printf(“%s.”, os)
}
In the above switch statement,”os” is a variable initialized to runtime.GOOS.
The switch will happen based on the value of ”os”. If any case doesn’t match,the default will be executed.
Example 2:
today := time.Now().Weekday()
switch time.Saturday {
case today + 0:
fmt.Println(“Today.”)
case today + 1:
fmt.Println(“Tomorrow.”)
case today + 2:
fmt.Println(“In two days.”)
default:
fmt.Println(“Too far away.”)
}
The case in the above example uses a variable “today”.
Example 3:Switch with no condition
Switch without a condition is the same as switch true
.This construct can be a clean way to write long if-then-else chains.
t := time.Now()
switch {
case t.Hour() < 12,:
fmt.Println(“Good morning!”)
case t.Hour() < 17:
fmt.Println(“Good afternoon.”)
default:
fmt.Println(“Good evening.”)
}
Example 4:Type Switch
var x interface{}
x=89
switch i := x.(type) {
case nil:
fmt.Printf("type of x :%T",i)
case int:
fmt.Printf("x is int")
case float64:
fmt.Printf("x is float64")
case func(int) float64:
fmt.Printf("x is func(int)")
case bool, string:
fmt.Printf("x is bool or string")
default:
fmt.Printf("don't know the type")
}
5. Exporting a functionality
In Go, a name is exported if it begins with a capital letter. For example, Pizza
is an exported name, as is Pi
, which is exported from the math
package.pizza
and pi
do not start with a capital letter, so they are not exported.
6. Importing a functionality
import m "math"
import "fmt"
It’s possible to either reference exported identifier with package name passed in import specification (m.Exp
) or with the name from the package clause of imported package (fmt.Println
).
There is another option which allows to access exported identifier without qualified identifier:
import (
“fmt”
. “math”
)
func main() {
fmt.Println(Exp2(6)) // Exp2 is defined inside math package
}
An import path is a string that uniquely identifies a package. A package’s import path corresponds to its location inside a workspace or in a remote repository. The packages from the standard library are given short import paths such as "fmt"
and "net/http"
. For your own packages, you must choose a base path that is unlikely to collide with future additions to the standard library or other external libraries.
Example: import “github.com/mlowicki/a”
Note: Golang’s compiler will yell if package is imported and not used.
To avoid compilation error use blank identifier: import _ “math”
Note: Go specification explicitly forbids circular imports — when package imports itself indirectly.
7. Zero values
Variables declared without an explicit initial value are given their zero value.The zero value is:
0
for numeric types,false
for the boolean type, and""
(the empty string) for strings.
8. Scopes and Blocks in GO
Block is a sequence of statements (empty sequence is also valid). Blocks can be nested and are denoted by curly brackets.
✔ for statement is in its own implicit block:
for i := 0; i < 5; i++ {
fmt.Println(i)
}
so variable i declared in init statement can be accessed in condition, post statement and nested block with loop body. Attempt to use i after for statement cause “undefined: i” compilation error.
✔ if statement is in its own implicit block
✔ switch statement is in its own implicit block
✔ each clause in a switch statement acts like a implicit block
switch i := 2; i * 4 {
case 8:
j := 0
fmt.Println(i, j)
default:
// "j" is undefined here
fmt.Println(“default”)
}// "j" is undefined here
Scope of identifier is a part of the source code (or even all) where identifier binds to certain value like variable, constant, package etc.
package mainimport “fmt”func main() { //Level 1
{ //Level 2
v := 1
{ //Level 3
fmt.Println(v) // v is accessible
} //Level 3
fmt.Println(v)
} //Level 2
// “undefined: v” compilation error
// fmt.Println(v)
} //Level 1
It behaves differently when:
v := 1 //declaration 1
{
v := 2 // //declaration scoped to the inner block
fmt.Println(v)
}
fmt.Println(v)
which prints:
2
1
Type declaration:
type X struct {
name string
next *X //This is possible
}x := X{name: “foo”, next: &X{name: “bar”}}
fmt.Println(x.name)
fmt.Println(x.next.name)
fmt.Println(x.next.next)
next field must be a pointer. It’s not possible to make declaration like:
type X struct {
name string
next X
}
since compiler will respond “invalid recursive type X” because while creating type X and in order to calculate its size compiler finds field next of type X for which it has to do the same — define its size so we’ve an infinite recursion. With pointer it’s doable as compiler knows the size of the pointer for desired architecture.
Package level scope:
// sandbox.go //File 1package mainfunc main() {
f() //defined in utils.go file
}
-----------------------------------------------------// utils.go //File 2package main //main package declared in file 2 as well.import “fmt”func f() {
fmt.Println(“It works!”)
}
Scopes while importing
While importing packages their names have scope set to file block.
// sandbox.go //File 1package mainimport “fmt”func main() {
fmt.Println(“main”)
f()
}
-----------------------------------------------------// utils.go //File 2package mainfunc f() {
fmt.Println(“f”)//compilation error with message“undefined:fmt”.
}
9. Methods in GO
On major difference between function and method is many methods can have same name while no two functions with same name can be defined in a package.This is because methods with same names can have different receivers.
package mainimport (
"fmt"
)type Vertex struct {
X, Y float64
}func (v Vertex) get(v1 *Vertex) {
v1.X = 9.76
}func (v Vertex) get2() {
v.X = 9.76
}func (v *Vertex) get3() {
v.X = 1.1
}func main() {v := Vertex{3.45, 4}v1 := Vertex{3.45, 4}v.get2()fmt.Println(v.X) //Prints 3.45v.get(&v1)fmt.Println(v1.X) // Prints 9.76v.get3()fmt.Println(v.X) //Print 1.1}
10.Slices
An array has a fixed size. A slice, on the other hand, is a dynamically-sized, flexible view into the elements of an array. In practice, slices are much more common than arrays.
The type []T
is a slice with elements of type T
.
A slice is formed by specifying two indices, a low and high bound, separated by a colon:
a[low : high]
This selects a half-open range which includes the first element, but excludes the last one.
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s) //prints [3 5 7]
Range
The range
form of the for
loop iterates over a slice or map.
When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index.
var testArray = []int{9,8,7}
for i, v := range testArray {
fmt.Printf("%d %d\n", i, v)
}
//prints
0 9
1 8
2 7
11.Function closures
Go functions may be closures. A closure is a function value that references variables from outside its body. The function may access and assign to the referenced variables; in this sense the function is “bound” to the variables.
For example, the adder
function returns a closure. Each closure is bound to its own sum
variable.
package mainimport "fmt"func adder(i int) func(int) int { //adder returns func(int) int
return func(x int) int {
i += x
return i
}
}func main() {
pos, neg := adder(1), adder(-10)
for i := 0; i < 3; i++ {
fmt.Println(
pos(i),
neg(i),
)
}
}//Output1 -10
2 -9
4 -7
12.Goroutines
A goroutine is a lightweight thread managed by the Go runtime.
go functionName(params)
starts a new goroutine running
functionName(params)
The evaluation of params happens in the current goroutine and the execution of function happens in the new goroutine.
Goroutines run in the same address space, so access to shared memory must be synchronized.
package mainimport (
"fmt"
"time"
)func say(s string) {
for i := 0; i < 2; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}func main() {
go say("world") //Invoking thread
say("hello")
}
13.Channels
Channels are a typed conduit through which you can send and receive values with the channel operator, <-
.
By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
The example code sums the numbers in a slice, distributing the work between two goroutines. Once both goroutines have completed their computation, it calculates the final result.
package mainimport "fmt"func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to channel c
}func main() {
s := []int{7, 2, 8, -9, 4, 0}c := make(chan int) //create channel
go sum(s[:len(s)/2], c) //invoke thread
go sum(s[len(s)/2:], c) //invoke thread
x,y := <-c, <-c // receive from cfmt.Println(x, y, x+y)
}//output
-5 17 12
14.Select
The select
statement lets a goroutine wait on multiple communication operations.A select
blocks until one of its cases can run, then it executes that case. It chooses one at random if multiple are ready.
package mainimport "fmt"func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x: //As long as channel c has value
x, y = y, x+y
case <-quit: //get the value from quit channel
fmt.Println("quit")
return //return from function
}
}
}func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 5; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}//Output
0
1
1
2
3
quit