I have been learning and using golang lately so here I am writing down the absolute basic exploration of Golang for anyone who would like to venture out into the ‘Go’ world.
Golang is simple, powerful and high performance language. It’s easy to learn and fun to write.
It is a combination of modern programming languages with the speed and reliability of low-level languages, making it a top choice for building robust and scalable applications.
All code snippets provided in this article can be run on a Go playground like here, if you wish to setup go in your local system checkout go.dev.
Hello world in Go
A simple hello world in Go looks like this.
1
2
3
4
5
6
7
8
|
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
|
main
is the entrypoint function for any go program.
import "fmt"
imports the ‘fmt’ package.
fmt.Println()
is an exported function from the ‘fmt’ package.
In go, the uppercase start of the function name, represent a publicly accesible function in that package.
Data types
In go the variable name is followed by its data type.
There are data types for string, number and boolean in Go. bool
is used for boolean, int is for integer and uint for unsigned integers . Float for floating point numbers.
The primitive data types consists of bool, string, numeric types that include Integer(int8,int16,int32,int64, uint, uint8, uint16, uint32, uint64), Floating point numbers(float32 and float64) and complex numbers (complex64 & complex128).
1
2
3
4
5
6
|
// data types
var a bool = true // Boolean
var b int = 5 // int is simply alias for 32 or 64 bit integer, depends on the machine architecture
var c float32 = 3.14 // Floating point numbers
var d string = "Hi!" // String
|
Zero values is default value assigned to declared variables that aren’t initialized. Zero value for numeric types is 0, for bool type is false and for strings is empty string “”.
Variable Declaration & Initialization
There are multiple ways to declare and initialize a variable. Check the below code snippet on how you can declare a variable and initialize it.
The := operator (known as walrus operator) is used for type inference so that the type can be infered from the value defined
Constant values can be defined with a const
keyword.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
var myvar2 string //declare
myvar2 = "Java" //initialize
var myvar1 = "Golang" //declare and initialize
//using a walrus operator
myvar3 := "Python"
//constant variables
const count = 5
const text string = "Hello world!"
fmt.Println("myvar1 = ", myvar1, ",myvar2 = ", myvar2, ",myvar3 = ", myvar3, "constant var ", count)
//Output: myvar1 = Golang ,myvar2 = Java ,myvar3 = Python count 5
|
Common data structures: arrays, slices, maps..
Arrays
An array type definition specifies a length and an element type. var a [5]int
specifies an array of integers of length 4.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package main
import "fmt"
func main() {
// a array of length 5
var a [5]int
a[0] = 1
fmt.Println(" array a ", a)
// another way to define an array
b := [6]string{"a", "b"}
fmt.Println(" array b ", b)
var twoD [3][3]int
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println(" twoD array", twoD)
/* output
array a [1 0 0 0 0]
array b [a b ]
twoD array [[0 1 2] [1 2 3] [2 3 4]]
*/
}
|
Slices
Slice is an abstraction on arrays unlike arrays it is more flexible and dynamically-sized.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
package main
import "fmt"
func main() {
// slices are alternatives to array
vowels := []string{"a", "e", "i", "o", "u"}
c := make([]int, 5)
// append to an existing slice
c[0] = 1
c[1] = 1
c[2] = 2
c[3] = 3
c[4] = 5
c = append(c, 8)
fmt.Println(" slice c ", c)
fmt.Println(" slice c ", c[2:4])
// 2d slice
twoDS := make([][]int, 3)
for i := 0; i < 3; i++ {
twoDS[i] = make([]int, 3)
for j := 0; j < 3; j++ {
twoDS[i][j] = i + j
}
}
fmt.Println(" twoD slices ", twoDS)
fmt.Println(" twoD slices ", twoDS[0:2][1:3])
/* output
slice c [1 1 2 3 5 8]
slice c [2 3]
twoD slices [[0 1 2] [1 2 3] [2 3 4]]
twoD slices [[1 2 3] [2 3 4]]
*/
}
|
Maps
Map is a data structure that stores collection of key-value pairs. The main operations on map includes setting a key-value, retrieving a value from key and deleting a key.
If a key doesn’t exists it’s zero value is returned.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package main
import "fmt"
func main() {
m := make(map[string]string)
m["key1"] = "value1"
m["key3"] = "value"
// second parameter denotes whether key is present or not
val1, _ := m["key1"]
fmt.Println("value of key1 = ", val1)
fmt.Println("value of key3 = ", m["key3"])
val, exists := m["key2"]
fmt.Println("value of key2 exists? ", exists, "value ", val)
delete(m,"key3") //deletes key3 from map
/* output
value of key1 = value1
value of key3 = value
value of key2 exists? false value
*/
}
|
if-else, switch, and for loops
if-else statement
In an if statement enclosing brackets around the condition is not necessary in Go.
1
2
3
4
5
6
7
8
9
10
11
|
const value = 101
if value%10 == 0 {
fmt.Println("Value is divisible by 10")
} else {
fmt.Println("Value is NOT divisible by 10")
}
// Output: Value is NOT divisible by 10
|
Variables can also be declared and initialized in an if-else statement.
1
2
3
4
5
6
7
|
if value := 101; value%10 == 0 {
fmt.Println("Value is divisible by 10")
} else {
fmt.Println("Value is NOT divisible by 10")
}
|
Switch
Switch in go doesn’t require a break
on each case, it is implicitly provided in each case. The values could be any other data type and need not be ca onstant or integer.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
const value = 101
switch value {
case 99:
fmt.Println("Value is 99")
case 100:
fmt.Println("Value is 100")
case 101:
fmt.Println("Value is 101")
default:
fmt.Println("Could not determine value")
}
// Output: Value is 101
|
Trickier part is switch without a condition. In such scenario, the case must be boolean and the first case that evaluates to true is executed. It can be helpful when we have a long chain of if else statements.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
text := "Hello World"
switch {
case text == "Bye":
fmt.Println("Text is Bye")
case text == "Hello World":
fmt.Println("Text is Hello World")
default:
fmt.Println("Text doesn't match")
}
|
Loops
Below is a for loop to sum elements from 1 to 10.
1
2
3
4
5
6
7
8
9
10
|
sum := 0
for i := 1; i <= 10; i++ {
sum += i
}
fmt.Println(sum)
// Output: 55
|
Go’s way to while
loops is different than that in other languages like Java or C++.
1
2
3
4
5
6
7
8
|
k := 1
//while version of Go
for k<=100 {
k += 10
}
|
An infinite loop
1
2
3
4
5
|
for {
// infinite loop, keep going
}
|
Functions
Functions can be defined like this func <func-name> (parameters) return-type
.
Go functions can accept and return multiple values.
Below code show usage of functions
add
accepts two values and returns sum of the values. We can also omit type when we have consecutive parameter of same type up to the final parameter like func add(a , b int) int
swap
function accepts two arguments and returns two values
sum
accepts any number of arguments of int
type and returns a int value.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
func swap(a int, b int) (int, int) {
return b, a
}
func sum(values ...int) int {
sum := 0
for i := 0; i < len(values); i++ {
sum += values[i]
}
return sum
}
func main() {
fmt.Println(add(10, 11))
a, b := swap(10, 11)
fmt.Println(a, b)
fmt.Println(sum(10, 11, 12, 13, 14, 15))
/*Output:
21
11 10
75
*/
}
|
Pointers
Pointers stores memory address of a value.
*int
denotes a pointer holding integer value.
*
operator denotes the value the address holds.
&
operator denotes the address of the operand.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var ptr *int; //declaration
mynum := 23
ptr = &mynum // ptr stores address of mynum
*ptr = *ptr+1 // operation on the ptr
fmt.Println("ptr value ", ptr)
fmt.Println("*ptr value ", *ptr)
// Output
// ptr value 0xc00001c030
// *ptr value 24
|
While passing a pointer to a function, we pass the reference to the value this allows us to change the value on other function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package main
import "fmt"
func swap(ptr1 *int, ptr2 *int) {
var c int = *ptr1
*ptr1 = *ptr2
*ptr2 = c
}
func main() {
a := 120
b := 300
swap(&a, &b)
fmt.Println(a, b)
/*Output:
300 120
*/
}
|
Structs
Struct is a collection of fields, they are helpful to create our own custom data type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
package main
import "fmt"
type User struct {
name string
age int
active bool
}
func main() {
p := User{"User1", 15, false}
p.age = 20
fmt.Println(p.age)
u := User{"User2", 25, true}
u.deactivate()
fmt.Println(u)
}
func (u *User) deactivate() {
(*u).active = false
}
func (u *User) activate() {
(*u).active = true
}
/* Output
20
{User2 25 false}
*/
|
Thing to note is while updating a struct inside other function one must pass it as a reference to the function otherwise the copy of the struct is updated which doesn’t update the original struct value.
The below functions wouldn’t work as expected as the user is being passed as a value.
1
2
3
4
5
6
|
func (u User) deactivate() {
u.active = false
}
func (u User) activate() {
u.active = true
}
|
Error Handling
Error handling is all together different in go. There is no stack trace and no try/catch mechanism, instead errors are just some value of type error
and are handled conditionally.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
package main
import (
"errors"
"fmt"
)
func divide(a int, b int) (int, error) {
if b == 0 {
return -1, errors.New("Can't divide by 0")
}
return a / b, nil
}
func main() {
out, err := divide(10, 0)
if err != nil {
fmt.Println("There is some error")
} else {
fmt.Println("The answer is ", out)
}
}
//Output: There is some error
|
There are few more topics like interfaces, generics, concurrency etc. that I have not included in this post to keep it simple and beginner friendly.
Some helpful resources to delve into more advanced topics: