Learning Go

gom

Go is an open-source, compiled, concurrent, imperative, structured, garbage-collected, system-programming language.

Also known as golang.

The reason I started learning Go is simple; the syntax.

The python-like syntax is very easy to adapt to, and just might be the best feature of this gem of a language, besides being very fast due to its compiled nature.

Isn't it developer utopia?

A language with python like syntax and C like performance!

Other major reasons include my inquisitiveness and a search engine company called Google, which develops and supports the language; also it is open-source!

Contents


Introduction

Development of Go commenced in September of 2007 at Google lead by Robert Griesemer, Rob Pike, and Ken Thompson.

Go became a public open source project on November 10, 2009. After a couple of years of very active design and development, stability was called for and Go 1 was released on March 28, 2012. Go 1, which includes a language specification, standard libraries, and custom tools, provides a stable foundation for creating reliable products, projects, and publications.

From Go FAQ

What is the purpose of the project?

No major systems language has emerged in over a decade, but over that time the computing landscape has changed tremendously. There are several trends:

Computers are enormously quicker but software development is not faster.

Dependency management is a big part of software development today but the “header files” of languages in the C tradition are antithetical to clean dependency analysis—and fast compilation.

There is a growing rebellion against cumbersome type systems like those of Java and C++, pushing people towards dynamically typed languages such as Python and JavaScript.

Some fundamental concepts such as garbage collection and parallel computation are not well supported by popular systems languages.

The emergence of multicore computers has generated worry and confusion. We believe it's worth trying again with a new language, a concurrent, garbage-collected language with fast compilation. Regarding the points above: It is possible to compile a large Go program in a few seconds on a single computer.

Go provides a model for software construction that makes dependency analysis easy and avoids much of the overhead of C-style include files and libraries.

Go's type system has no hierarchy, so no time is spent defining the relationships between types. Also, although Go has static types the language attempts to make types feel lighter weight than in typical OO languages.

Go is fully garbage-collected and provides fundamental support for concurrent execution and communication.

By its design, Go proposes an approach for the construction of system software on multicore machines.


Hello World

        package main
        import "fmt"
        func main () {
            fmt.Println("Hello, World!")
        }

The command line front-end for Go is go, file names end with a .go extension. The go build file-name command produces an executable binary.

Go insists on having your code organized in a specific manner. The GOPATH environment variable in your operating system should specify your workspace, from the Go doc:

Workspaces

The go tool is designed to work with open source code maintained in public repositories. Although you don't need to publish your code, the model for how the environment is set up works the same whether you do or not.

Go code must be kept inside a workspace. A workspace is a directory hierarchy with three directories at its root:

The go tool builds source packages and installs the resulting binaries to the pkg and bin directories.

The src subdirectory typically contains multiple version control repositories (such as for Git or Mercurial) that track the development of one or more source packages.

The user works in the src subdirectory in a module's folder. Where the module is a program file that you create.

go command

The go command can be used as follows.

    syntax: go <command> <arguments>

Here arguments refer to Go source code files, commonly used commands:


Packages

The first statement in a Go source file must be the package name where name is the package's default name for imports. (All files in a package must use the same name.)

Go's convention is that the package name is the last element of the import path: the package imported as "crypto/rot13" should be named rot13.

Executable commands must always use package main.

There is no requirement that package names be unique across all packages linked into a single binary, only that the import paths (their full file names) be unique.

The following line demonstrates a Go import.

        syntax: import "<package name>"

Multiple imports are possible by using tuple-like declarations.

        syntax: import ("<package-1 name>" "<package-2 name>")

fmt package is the stdio.h equivalent. Print and Println functions are present like in Java. Printf is also present which provides the much required formatted output programming.

Some important packages: fmt, net, os, time, math

Using Println from fmt.

        syntax: fmt.Println(<arg>)

After importing a package, one can refer to the names it exports. In Go, a name is exported if it begins with a capital letter.


Variables

Variables are declared differently as compared to other traditional languages. The var keyword declares a variable followed by the name followed by the type.

        syntax: var <name> type         //normal variable
        syntax: var <name> [size]type   //array variable

Variables are intialized with default values; for integers and floats 0 and 0.0 respectively and 'false' for booleans.

Declarations can include initializers one per variable. If initial value is present the type can be omitted.

        syntax: var x, y, z int = 1, 2, 3
        syntax: var c, python, java = true, false, "no!" //java is string type

Short declaration can be used inside functions with := omitting the 'var' keyword.

            //code
            package main
            import "fmt"

            func main() {
                var x, y, z int = 1, 2, 3
                c, python, java := true, false, "no!"
                fmt.Println(x, y, z, c, python, java)
            } 
Variables can be declared as constants by replacing 'var' with the 'const' keyword. High precision constant numeric values take their type through context.

Selective Flow

if-else

if-else in Go is similar to the general if-else construct of other languages, though there are some minor differences.

            //code
            package main
            import "fmt"
            func main() {
                var a,b int = 1,2
                if a>b {
                    fmt.Println("a is bigger.")
                } else {
                    fmt.Println("b is bigger.")
                }
            }

Similar to for-loop if can have a pre-condition to execute before checking the condition.

            //code
            package main
            import (
                "fmt"
                "math"
            )
            func pow(x, n, lim float64) float64 {
                if v := math.Pow(x, n); v < lim {
                    return v
                }
                return lim
            }
            func main() {
                fmt.Println(
                    pow(3, 2, 10),
                    pow(3, 3, 20),
                )
            }

Variables declared inside a short if statement are also accessible by the else block.

switch-case

The switch statement provides an alternative to the if-else-if ladder.

            //code
            package main
            import (
                "fmt"
                "runtime"
            )
            func main() {
                fmt.Print("Go runs on ")
                switch os := runtime.GOOS; os {
                case "darwin":
                    fmt.Println("OS X.")
                case "linux":
                    fmt.Println("Linux.")
                default:
                    // freebsd, openbsd,
                    // plan9, windows...
                    fmt.Printf("%s.", os)
                }
            }

Like all other constructs the switch-case construct also supports a pre-condition to execute before performing the comparisons. Case body breaks automatically unless it ends with a fallthrough statement.

In Go, switch-case works with data-types such as a string. In contrast to languages like C which provide switch-case only for primitive types such as ints and char.

A switch with no condition behaves as switch true, essentially a cleaner way to represent an if-else-if ladder.

            //code
            package main
            import (
                "fmt"
                "time"
            )
                func main() {
                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.")
                }
            }

Iterative Flow

Go has only one looping construct the for loop.

            //code
            package main
            import "fmt"
            func main() {
                sum := 0
                for i := 0; i < 10; i++ {
                    sum += i
                }
                fmt.Println(sum)
            }

As in Java, you can leave the initial and final steps empty, similarly, in Go you can drop the extraneous semi-colons and the construct then behaves as a while loop.

            //code
            package main
            import "fmt"
            func main () {
                i:= 0
                for i<10 {
                    fmt.Println (i+1)
                }
            }

Pointers

Go has support for pointers.

        syntax: var <name> *<type> = &<variable>
        syntax: var <name> *<type> = new <type>
        syntax: <name> := new <type>

Go is memory safe hence, pointer arithmetic is not permitted.

Values associated with pointers can be accessed with the * operator.

            //code
            package main
            import "fmt"

            func main() {
                p := 5
                q := &p

                fmt.Println("p =", p)               
                fmt.Println("q =", q)

                fmt.Println("p =", *q)
            }

Arrays

Arrays in Go are directly influenced by lists in Python; the following behaves exactly as it behaves in Python.

            //code
            package main
            import "fmt"

            func main() {
                p := []int{2, 3, 5, 7, 11, 13}

                fmt.Println("p ==", p)
                fmt.Println("p[1:4] ==", p[1:4])
                fmt.Println("p[:3] ==", p[:3])
                fmt.Println("p[4:] ==", p[4:])
            }

For those who do not know Python.

        syntax: p := []int {1,2,3} //initializes an array
        syntax: p[start:end]

The above means a slice of the array that starts from the start index and goes upto end-1 index inclusive.

In simpler words slice begins at start position and contains end-start elements.

        syntax: p[:end] // is same as p[0:end]

The above syntax refers to a slice with all the elements from the 0 index, till it has end elements.

        syntax: p[start:] // is same as p[start:len(p)]

This syntax slices from and including the start index to the end of the array.

Slices can also be created with the make function

        syntax: make ([]type, <length>, <capacity>)

The capacity parameter is optional and if not provided, defaults to the given value of length.

An array's length can be accessed through the len() function and its maximum capacity can be obtained by using the cap() function.

Note: In Go nil is equivalent to None/null/NULL of other languages, signifying nothing.

Go also provides advanced for loops like Python.

        //code
        package main
        import "fmt"

        var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

        func main() {
            for i, v := range pow {
            fmt.Printf("2**%d = %d\n", i, v)
            }
        }

Alternative advanced for loops include the following:

        //code
        package main
        import "fmt"

        func main() {
            pow := make([]int, 10)
            for i := range pow {
                pow[i] = 1 << uint(i)
            }
            for _, value := range pow {
                fmt.Printf("%d\n", value)
            }
        }

Functions

Functions are declared with func keyword. The function prototype is different than other traditional languages.

            //code
            package main
            import "fmt"

            func add(x int, y int) int {
                return x + y
            }
            func main() {
                fmt.Println(add(42, 13))
            }

Prototype is func <name> (arg <type>) <return type>

Functions can take zero or more inputs. If multiple inputs are of the same type then the type specifier can be omitted from all but the last argument.

    syntax: function (x int, y int) int //this can be rewritten as
    syntax: function (x, y int) int     //also correct

Like Python functions can also return multiple values.

        //code
        package main
        import "fmt"

        func swap(x, y string) (string, string) {
            return y, x
        }
        func main() {
            a, b := swap("hello", "world")
            fmt.Println(a, b)
        }

The return parameter in the prototype can be declared in the prototype, hence value/s can be returned with just the return statement.

        //code
        package main
        import "fmt"

        func split(sum int) (x, y int) {
            x = sum * 4 / 9
            y = sum - x
            return
        }
        func main() {
            fmt.Println(split(17))
        }

Note: Functions are values too and hence can be assigned to variables.


Structures

Go has support for structures. The following demonstrates a struct for storing coordinates:

            //code
            package main
            import "fmt"
            type Vertex struct {
                X int
                Y int
            }
            func main() {
                fmt.Println(Vertex{1, 2})
            }

Note: the type keyword is the Go equivalent of C's typedef. Structs can be assigned to variables in the following manners:

        syntax: var <name> <struct-name> 
        syntax: <name>:= <struct-name>{}
        syntax: <name>:= new(struct-name>) 
        syntax: var (
                        p = Vertex{1, 2}  // has type Vertex
                        q = &Vertex{1, 2} // has type *Vertex
                        r = Vertex{X: 1}  // Y:0 is implicit
                        s = Vertex{}      // X:0 and Y:0
                    )

The variables can be accessed by using the . operator. Structs can also be accessed through pointers. Though unlike the -> operator, pointers also use the . operator to access sub-fields.


Maps

Go has an in-built map type that implements a hash-map where each entry is a key-value pair similar to that of a dictionary in Python.

        //code
        package main
        import "fmt"

        var m map[string]int
        func main() {
            m = make(map[string]int)
            m["Author"] = 151294
            fmt.Println(m["Author"])
        }

Maps are made with make keyword (not new)

Map literals demonstration:

        //code
        package main
        import "fmt"

        type Vertex struct {
            Lat, Long float64
        }
        var m = map[string]Vertex{
            "Bell Labs": Vertex{
                40.68433, -74.39967,
            },
            "Google": Vertex{
                37.42202, -122.08408,
            },
        }
        func main() {
            fmt.Println(m)
        }

If the top-level is a type name (as the above example) it can be removed.

    syntax: var m = map[string]Vertex{
                        "Bell Labs": {40.68433, -74.39967},
                        "Google":    {37.42202, -122.08408},
                    }

Basic map operations: