GoLang Basics

Welcome to the basics part of GoLang Programming

Table Of Contents

Running Go Code

Let’s start our journey by creating a simple program and learning how to compile and execute it. Open your favorite text editor and write the following code, save the file as main.go

package main

import "fmt"

// In Go, the entry point to a program has to be a
// function called `main` within a package `main`
func main() {
    println("Hello, World")
}

Finally, run the program by entering the following command

$ go run main.go
Hello, World

What about compilation though? go run is a handy command that compiles and runs your code. It uses a temporary directory to build the program, executes it and then cleans itself up. You can see the location of the temporary file by running

$ go run --work main.go

To explicitly compile code, use go build

$ go build main.go

This will generate an executable main which you can run. On Linux, don’t forget that you need to prefix the executable with dot-slash, so you need to type ./main. While developing, you can use either go run or go build. When you deploy your code however, you’ll want to deploy a binary via go build and execute that.

We’ll talk more about packages in a later chapter. For now, while we focus on understanding the basics of Go, we’ll always write our code within the main package.

Imports : In Go, the import keyword is used to declare the packages that are used by the code. We’re using one of Go’s standard packages - fmt. You’ve probably noticed we prefix the function name with the package, e.g., fmt.Println. Go is strict about importing packages. It will not compile if you import a package but don’t use it. Try to run the following

package main

import "fmt"

func main() {
}

Output

$ go run main.go
# command-line-arguments
main.go:4:2: imported and not used: "fmt"

go get : Go is strict about this because unused imports can slow compilation; admittedly a problem most of us don’t have to this degree. Another thing to note is that Go’s standard library is well documented. You can head over to https://golang.org/pkg/fmt/#Println to learn more about the Println function that we used. To install godoc use the go get command

$ go get -v golang.org/x/tools/cmd/godoc
$ go doc fmt.Println
package fmt // import "fmt"

func Println(a ...interface{}) (n int, err error)
    Println formats using the default formats for its operands and writes to
    standard output. Spaces are always added between operands and a newline is
    appended. It returns the number of bytes written and any write error
    encountered.

If you’re ever stuck without internet access, you can get the documentation running locally via godoc

$ sudo apt install golang-golang-x-tools
$ godoc

And Navigate to http://localhost:6060 on the browser

Variables - Declarations and Types

Example code

package main

import "fmt"

func main() {
    var variableA int
    variableA = 20
    variableB := 30
    fmt.Println(variableA, " ", variableB)
}

Here, we declare two variables variableA and variableB using to different declarations. The first one we declare a variable variableA of type int. By default, Go assigns a zero value to variables. Integers are assigned 0, booleans false, strings "" and so on. Next, we assign 20 to our variableA. We can merge the first two lines

var variableA int = 20

Still, that’s a lot of typing. Go has a handy short variable declaration operator, :=, which can infer the type

variableB := 30

Types : Go has various types including strings, integers, floats, booleans, etc. The TypeOf() method of the reflect package is used to determine the datatype of the variables. Here’s an example code

package main

import (
    "fmt"
    "reflect"
)

func main() {
    // strings can be added together with +
    fmt.Println("go" + "lang", reflect.TypeOf("go"))
    // integers
    fmt.Println("1 + 2 =", 1+2, reflect.TypeOf(1))
    // floats
    fmt.Println(7.0/2, reflect.TypeOf(7.0/2))
    // booleans
    fmt.Println(true && false, true || false, !true, reflect.TypeOf(true))
}

Output

$ go run main.go 
golang string
1 + 2 = 3 int
3.5 float64
false true false bool
  • Basic Types

    Go’s basic types are

      * bool
      * string
      * int - int8 - int16 - int 32 - int64
      * uint - uint8 - uint16 - uint 32 - uint64 - uintptr
      * byte // alias for uint8
      * rune // alias for int32, represents a Unicode code point
      * float32 - float64
      * complex64 - complex128
    

    Example

      package main
    
      import "fmt"
    
      func main() {
          var (
              boolVariable bool = false
              stringVariable string = "string"
              int64Variable int64 = 1<<63 - 1
              uint64Variable uint64 = 1<<64 - 1
              byteVariable byte = 'A'
              runeVariable rune = '\a'
              float64Variable float64 = 345677.9876
              complex128Variable complex128 = -5 + 12i
          )
    
          fmt.Printf("Type: %T       Value: %v\n", boolVariable, boolVariable)
          fmt.Printf("Type: %T     Value: %v\n", stringVariable, stringVariable)
          fmt.Printf("Type: %T      Value: %v\n", int64Variable, int64Variable)
          fmt.Printf("Type: %T     Value: %v\n", uint64Variable, uint64Variable)
          fmt.Printf("Type: %T      Value: %v\n", byteVariable, byteVariable)
          fmt.Printf("Type: %T      Value: %v   Unicode: %U\n", runeVariable, runeVariable, runeVariable)
          fmt.Printf("Type: %T    Value: %v\n", float64Variable, float64Variable)
          fmt.Printf("Type: %T Value: %v\n", complex128Variable, complex128Variable)
      }
    

    Output

      $ go run main.go 
      Type: bool       Value: false
      Type: string     Value: string
      Type: int64      Value: 9223372036854775807
      Type: uint64     Value: 18446744073709551615
      Type: uint8      Value: 65
      Type: int32      Value: 7   Unicode: U+0007
      Type: float64    Value: 345677.9876
      Type: complex128 Value: (-5+12i)
    
  • Maps

    Maps in Go are what other languages call hashtables or dictionaries. They work as you expect: you define a key and value, and can get, set and delete values from it. Let’s look at an example

      package main
    
      import "fmt"
    
      func main() {
          // declaring a map, maps grow dynamically
          mapVariable := make(map[string]int)
    
          // assigning values to a map
          mapVariable["a"] = 1
          mapVariable["b"] = 2
          fmt.Println(mapVariable)
          fmt.Println(len(mapVariable))
    
          // we can also specify an initial size for better performance
          mapVariable1 := make(map[string]int, 2)
          fmt.Println(mapVariable1)
          fmt.Println(len(mapVariable1))
    
          // we can also assign values while declaring it
          mapVariable2 := map[string]int{"a":1, "b":2}
    
          // accessing values in a map
          value, exists := mapVariable2["a"]
          fmt.Println(value, exists)
    
          value, exists = mapVariable2["c"]
          fmt.Println(value, exists)
    
          // deleting a value in a map
          delete(mapVariable2, "b")
          // deleting a value that does not exist does not throw an error
          delete(mapVariable2, "c")
    
          // Iteration over maps isn’t ordered. Each iteration over
          // a lookup will return the key value pair in a random order
          for key, value := range mapVariable {
              fmt.Println(key, " : ", value)
          }
      }
    

    Output

      $ go run golang/codes/main.go 
      map[a:1 b:2]
      2
      map[]
      0
      1 true
      0 false
      a  :  1
      b  :  2
    
  • Arrays

    In Go, like C, C++ arrays size is fixed. Declaring an array requires that we specify the size, and once the size is specified, it cannot grow. ttempts to access an outof range index in the array will result in a compiler or runtime error. Let’s look at an example for declaring, assigning and accessing an array

      package main
    
      import "fmt"
    
      func main() {
          var subjects[7]string
          subjects[0] = "maths"
          subjects[1] = "english"
    
          // length of an array
          fmt.Printf("subjects length is %v\n", len(subjects))
    
          fmt.Println(subjects)
    
          // initialize an array with values
          subjects1 := [2]string{"maths", "english"}
    
          // iterating through an array
          for index, value := range subjects1 {
              fmt.Printf("index: %d value: %s\n", index, value)
          }
      }
    

    Arrays are efficient but rigid. We often don’t know the number of elements we’ll be dealing with upfront. For this, we turn to slices.

  • Slices

Constants : Go supports constants of different types. const declares a constant value. A constant has no type until it’s given one, such as by an explicit conversion

const s string = "string constant"
const i1 int = 30
const i2 = 40

Note #1 : It’s important that you remember that := is used to declare the variable as well as assign a value to it. Why? Becausea variable can’t be declared twice (not in the same scope anyway). If you try to run the following, you’ll get an error.

package main

import "fmt"

func main() {
    varA := 20
    fmt.Println(varA);
    varA := 30 // will throw error
    fmt.Printlb(varB);
}

Output

$ go run main.go
# command-line-arguments
main.go:10:10: no new variables on left side of :=

The above error means that when we first declare a variable,we use := but on subsequent assignment, we use the assignment operator =. If you read the error message closely, you’ll notice that variables is plural. That’s because Go lets you assign multiple variables (using either = or :=).

// as long as one of the variables is new := can be used
varA, varB := 20, 30
fmt.Println(varA, varB)
varB, varC := 25, 35
fmt.Println(varB, varC)

Note #2 : Go won’t let you have unused variables. Like unused imports it’ll throw an error when unused variables are present. Let’s look at an example

package main

import (
    "fmt"
)

func main() {
    varA, varB := 20, 30
    fmt.Println(varA);
}

Output

$ go run main.go
# command-line-arguments
main.go:8:11: varB declared and not used

Functions

Like many other languages, functions in Golang allow us to divide code into useful blocks, make it more readable, reuse it. Like in Python, functions in Golang can return multiple values. We declare functions in Golang using the func keyword. Let’s look at some example

// takes in one argument of int type and no return value
func function1(arg1 int) {
}

// takes in two arguments, one of int and the other is a string
// and returns an int value
func function2(arg1 int, arg2 string) int {
    return 1
}

// takes in two arguments, both of type int and returns two
// values one is int and the other is a string
func function3(arg1, arg2 int) (int, string) {
    return 1, "1"
}
// see the shorthand for argument types, if they are of the
// same type then we can do the above instead of explicitly specifying the type

In the last case we can use

a, b := function3(1, 3)
// if we want to ignore the first value then we can do so by using _
_, b := function3(1, 3)

This is more than a convention. _, the blank identifier, is special in that the return value isn’t actually assigned This lets you use _ over and over again regardless of the returned type.

Structures

GoLang isn’t an object-oriented (OO) language like C++ and Java. It doesn’t have objects nor inheritance and thus, doesn’t have the many concepts associated with OO such as polymorphism and overloading. But GoLang does have structures , which can be associated with methods. It supports a simple but effective form of composition. Although Go doesn’t do OO like you may be used to, you’ll notice a lot of similarities between the definition of a structureand that of a class. The following is the syntax for declaring a struct in Go

// type and struct are keywords
// Person is the name of the structure
// Name and Age are attributes of the Person structure
type Person struct {
    Name string
    Age int
}

Let’s take a look at Declaring and Initializing the struct

package main

import (
    "fmt"
)

type Person struct {
    Name string
    Age int
}

func main() {
    p1 := Person{
        Name: "John",
        Age: 22,
    } // the trailing , in the above structure is required
    fmt.Println(p1)

    p2 := Person{}
    // Just like unassigned variables have a zero value, so do fields.
    fmt.Println(p2)
    // or
    p2 = Person{Name: "Jane"}
    fmt.Println(p2)
    p2.Age = 24
    fmt.Println(p2)
}

Output

$ go run golang/codes/main.go 
{John 22}
{ 0}
{Jane 0}
{Jane 24}

Pointers : Many times though, we don’t want a variable that is directly associated with our value but rather variable that has a pointer to our value. A pointer is a memory address; it’s the location of where to find the actual value. Why do we need pointers, lets look at an example

func main() {
    p1 := Person("John", 20)
    increaseAge(p1)
    fmt.Println(p1)

    p2 := Person("John", 20)
    increaseAge1(&p2)
    fmt.Println(p2)
}

func increaseAge(p Person) {
    p1.Age += 1
}

func increaseAge1(p *Person) {
    p1.Age += 1
}

Output

$ go run main.go 
{John 20}
{John 21}

The above happens because increaseAge makes changes to the copy of the p1 and thus, changes made in increaseAge weren’t reflected in the caller. To make this work, we need to pass a pointer to the function as is the case for increaseAge1 and p2.

Note #1 : The & operator is used to get the address of a value, whereas *x means pointer to the value of type X.

Creating Structures : Despite the lack of constructors, Go does have a built-in new function which is used to allocate the memory required by the type.

p1 := new(Person)
// is the same as
p1 := &Person{}

Which you use is upto you, but you’ll find that most people prefer the latter. A structures don’t have constructors, many prefer to create a function that returns an instance of the desired type

func NewPerson(name string, age int) *Person {
    return &Person{
        Name: name,
        Age: age,
    }
}

Methods on Structures : We can associate a method with a structure, like the way we associate methods in classes.

type Person struct {
    Name string
    Age int
}

func (p *Person) increaseAge() {
    p.Age += 1
}

In the above code, we say that the type *Person is the receiver of the increaseAge method. We call increaseAge as follows

p1 = Person{"John", 20}
p1.increaseAge()
fmt.Println(p1.Age)

Note #2 : The fields of a struct can be of any type - including arrays, maps, interfaces and functions.

Note #3 : Composition in Go is similar to Java, except that we don’t have to duplicate every method.

package main

import "fmt"

type Person struct {
    Name string
    Age int
}

func (p *Person) getName() {
    fmt.Println("Hi!! I am " + p.Name)
}


type Teacher struct {
    // The Teacher structure has a field of type *Person. Because we didn’t
    // give it an explicit field name, we can implicitly access the fields
    // and functions of the composed type
    *Person
    Subject string
}

func main() {
    t1 = &Teacher{
        Person: &Person{"John", 22},
        Subject: "Maths",
    }

    t1.getName()
    
    // both will print the same
    fmt.Println(t1.Name)
    fmt.Println(t1.Person.Name)
}

Output

$ go run main.go
Hi!! I am John
John
John

Implicit composition is really just a compiler trick, we can “overwrite” the functions of a composed type - Teacher can have it’s own getName method

func (t *Teacher) getName() {
    fmt.Println("Hi!! I am " + p.Name + " the Teacher")
}
// can access this by t.getName()
// to access the original person function - t.Person.getName()

Interfaces

Interfaces are types that define a structure and it’s methods but not an implementation. Let’s look at an example

type Parser interface {
    Parse() string
}

You might be wondering what purpose this could possibly serve. Interfaces help decouple your code from specificimplementations. For example, we might have various types of parsers:

type StringParser struct { ... }
type IntegerParser struct { ... }
type BooleanParser struct { ... }

Yet by programming against the interface, rather than these concrete implementations we can easily change (and test) which we use without any impact to our code. In Java, we have to be explicit when a class implements an interface, in Go this happens implicitly. If your structure has a function named Parse with a string return value, then it can be used as a Parser.

type IntegerParser struct {
    Message int
}
func (p StringParser) Parse() string {
    return string(p.Message)
}

Interfaces can also participate in composition. And, interfaces themselves can be composed of other interfaces. Finally, interfaces are commonly used to avoid cyclical imports. Since they don’t have implementations, they’ll havelimited dependencies. Let’s look at complete example of interfaces

package main

import (
    "fmt"
    "strconv"
)

type Parser interface {
    Parse() string
}

type IntegerParser struct {
    Message int
}

func (ip IntegerParser) Parse() string {
    return strconv.Itoa(ip.Message)
}

type BooleanParser struct {
    Message bool
}

func (ip BooleanParser) Parse() string {
    return strconv.FormatBool(ip.Message)
}

func main() {
    var example Parser = IntegerParser{Message: 200}
    fmt.Println(example.Parse())

    var example1 Parser = BooleanParser{Message: false}
    fmt.Println(example1.Parse())
}

Output

$ go run main.go 
200
false

Code Organization

  • Package

    To keep more complicated libraries and systems organized, we need to learn about packages. In Go, package names follow the directory structure of your Go workspace. When you name a package, via the package keyword, you provide a single value, not a complete hierarchy. When you import a package, you specify the complete path.

    Sample project structure

      - person
        - person.go
        - teacher
          - teacher.go
        - student
          - student.go
      - main
        - main.go
    

    To import teacher package functions in the main, we need to specify the full path - person/teacher. To import student package functions in person package, just specify the package name - person/student.

  • Visibility

    Go uses a simple rule to define what types and functions are visible outside of a package. If the name of the type or function starts with an uppercase letter, it’s visible. If it starts with a lowercase letter, it isn’t. This also applies to structure fields. If a structure field name starts with a lowercase letter, only code within the same package will be able to access them.

      package example
    
      func NewFunc() { ... }
    

    It could be called via example.NewFunc(). But if the function was named newFunc, we wouldn’t be able to access it from a different package.

  • Package Management

    The go get command which is used to fetch third-party libraries, also supports various protocols like in this example - we’ll be getting a library from Github, meaning,you’ll need git installed on your computer. Assuming you already have git installed, from a shell/command prompt, enter:

      go get github.com/mattn/go-sqlite3
    

    go get fetches the remote files and stores them in your workspace. Go ahead and check your $GOPATH/src. You’ll now see a github.com folder. Within, you’ll see a mattn folder which contains a go-sqlite3 folder. We just talked about how to import packages that live in our workspace. To use our newly gotten go-sqlite3 package, we’d import as follows

      import (
          "github.com/mattn/go-sqlite3"
      )
    

    This looks like a URL but in reality, it’ll simply import the go-sqlite3 package which it expects to find in $GOPATH/src/github.com/mattn/go-sqlite3.

    Note # : As you start writing more complex systems, you’re bound to run into cyclical imports. This happens when package A imports package B but package B imports package A (either directly or indirectly through another package). This is something the compiler won’t allow.

  • Dependency Management

    As discussed above, If we go get with in a project, it’ll scan all the files, looking for imports to third-party libraries and will download them. In a way, our own source code becomes a Gemfile or package.json. If you call go get -u it’ll update the packages (or you can update a specific package via go get -u FULL_PACKAGE_NAME). Eventually, you might find go get inadequate. For one thing, there’s no way to specify a revision, it always points to the master/head/trunk/default. This is an even larger problem if you have two projects needing different versions of the same library.

  • Project Layout