Introduction

This is a part of #60DaysOfLearning challenge by Leapfrog.

I want to start my journey with DevOps. So, I am starting with learning Go. I will explore Go in depth for this week and move on to more tools.

Go has become a de facto language for cloud orchestration and software in the larger world.(from Kubernetes to Docker). I used to do Python. But seems like Python has some problems at scale, from running out of memory to The Global Interpreter Lock|(GIL) preventing true multi threading. And, the runtime errors due to dynamic typing are a bit of pain.

This is a collection of things I learned today.

This post assumes you have knowledge of at least one programming language, preferably C and/or Python.

Package Management

Go divides programs into packages, and all go files in a directory must belong to the same package.

Declaring a package is as simple as the following:

package main

package main is special. All other package names declare a package that must be imported into another package to be used. Package main will declare func main(), which is the starting point for a binary to run.

Let’s say you have a directory structure as follows:

mypackage/
	file1.go
	file2.go

Then, file1.go and file2.go should have the following:

package main

When mypackage is imported by another package, it will include everything declared in all files in the mypackage directory.

To import a package, you basically have two general types.Standard library packages stand out because they don’t list some repository information in their path, such as the following:

"fmt"
"encoding/json"
"archive/zip"

All other packages generally have repository information preceding them, as follows:

"github.com/johnsiilver/golib/lru"
"github.com/kylelemons/godebug/pretty"

And every imported package must be used at least once, otherwise if you need to do a side effects import, in which just loading the package causes something to happen, but you don’t use the package. This should always be done in package main and requires prepending with an underscore (_):

package main
import (
  "fmt"
  _ "github.com/kylelemons/godebug/pretty"//Just an example
)
func main() {
 fmt.Println("Hello, playground")
}

A basic Hello World program

package main

import "fmt"

func main() {
	hello := "Hello World!"
	fmt.Println(hello)
}

Here := means create a variable and assign the string value to it. You could use var hello to just initialize. Go syntax is similar to C and scopes are defined with curly braces.

Zero Values

In some older languages, a variable declaration without an assignment has an unknown value. This is because the program creates a place in memory to store the value but doesn’t put anything in it. So, the bits representing the value are set to whatever happened to be in that memory space before you created the variable. But in Go, declaring a variable without an assignment automatically assigns a value called the zero value.

Zero Values in Go

Function/statement variable should be used

The rule here is that if you create a variable within a function or statement, it must be used. This is much for the same reason as package imports; declaring a variable that isn’t used is almost always a mistake.

This can be relaxed in much the same way as an import, using _.

_ = someVar

Looping, Branching, and Functions

As it is just some syntactical changes, I will not be discussing these in the post.

However, as in Python, functions in Go supports multiple variables to return.

package main

import "fmt"

func divide(num, div int) (res, rem int) {
	result = num / div
	remainder = num % div
	return res, rem
}

func main() {
	result, remainder := divide(3, 2)
	fmt.Printf("Result: %d, Remainder %d", result, remainder)
}

Varidac Arguments

A variadic argument is when you want to provide 0 to infinite arguments. A good example would be calculating a sum of integers.

Here is an example:

package main

import "fmt"

func sum(nums ...int) {
    total := 0
    for _, num := range nums {
        total += num
    }
    fmt.Println(total)
}

func main() {
    sum(1, 2)
    sum(1, 2, 3)
}

In this example, nums is a slice of integers. The range keyword is used to iterate over each element of the nums .

Anonymous Functions

Go has a concept of anonymous functions, which means a function without a name (also called a function closure).

func main() {
	result := func(word1, word2 string) string {
							return word1 + " " + word2
				 }("hello", "world")
	fmt.Println(result)
}

Defining Private and Public

Just remember a rule: To be public, the constant/variable/function/method must simply start with an uppercase letter. If it starts with a lowercase letter, it is private.

Structures, Methods and Pointers

Go supports the use of pointers, as every call to a function is call by value, it is necessary. However, in most use cases, we need not go overboard with pointers. Structures and methods are defined as the code example below.

type Record struct{
 Name string
 Age int
}
// a method for the struct
func (r Record) String() string {
 return fmt.Sprintf("%s,%d", r.Name, r.Age)
}

And, Go doesn’t provide any specialized code for that, instead, we use a constructor pattern using simple functions.

func NewRecord(name string, age int) (*Record, error) {
	if name == "" {
		return nil, fmt.Errorf("name cannot be the empty string")
	}
	if age <= 0 {
		return nil, fmt.Errorf("age cannot be <= 0")
	}
	return &Record{Name: name, Age: age}, nil
}

This can be then implemented in code as:

rec, err := NewRecord("John Doak", 100)
 if err != nil {
 return err
}

We will explore errors in the coming days.

Understanding Interface in GO

In Go, an interface is a set of method signatures. When a type provides the definition for all the methods in the interface, it is said to implement the interface. It provides a way to achieve runtime polymorphism. An interface can be defined using the type keyword, followed by the name of the interface and the interface keyword.

Here is an example:

type Writer interface {
	Write([]byte) (int, error)
}

In this example, any type that defines a method Write with the exact signature is said to satisfy the Writer interface. Interfaces can be embedded in other interfaces to create composite interfaces.

type Reader interface {
	Read([]byte) (int, error)
}

type Writer interface {
	Write([]byte) (int, error)
}

type ReadWriter interface {
	Reader
	Writer
}

In this case, any type that satisfies both Reader and Writer interfaces satisfies the ReadWriter interface. Remember, Go interfaces are implemented implicitly, so there’s no need to explicitly declare that a type implements an interface.

Conclusion

As we’ve explored in this post, understanding Go’s package management, functions, loops, and interfaces provides a solid foundation for diving deeper into the language in the further days. Feel free to suggest changes in the article and look out for more posts.