What I liked while learning GO language?

Arun Rajeevan
7 min readFeb 18, 2019

--

  1. No need of Semi-Colons to end a statement.
  2. Compilation error if you haven’t used a declared variable.
  3. 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 “fmtfunc 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 c
fmt.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

--

--

No responses yet