Go at a Glance — A Language a Day, Advent Calendar Day 17/24

About this ‘A Language a Day’ Advent Calendar

Andrew Shitov. A Language a Day

This article became a part of my book ‘A Language a Day’, which you can get in both electronic and paper format. Languages covered in the book: C++, Clojure, Crystal, D, Dart, Elixir, Factor, Go, Hack, Hy, Io, Julia, Kotlin, Lua, Mercury, Nim, OCaml, Raku, Rust, Scala, and TypeScript.

Learn more where to get the book

Welcome to Day 17 of this year’s A Language a Day Advent Calendar. Today’s topic is introduction to the Go programming language.

Facts about the language

Some facts about the Go programming language

  • Statically typed
  • Compiled language
  • Fast compilation
  • Enforced code style
  • Design at Google
  • Appeared in 2009
  • Website: golang.org

Installing and running Go

Go offers a lot of click-and-go packages that you can immediately use to install the language tools on your operating system. Having that done, you can run a program:

$ go run hello-world.go

You can also make an executable binary file by compiling the program:

$ go build hello-world.go 
$ ./hello-world 

Hello, World!

A minimal program that prints a message is quite wordy and requires creating the main() function and importing a package to be able to generate output:

package main

import "fmt"

func main() {
    fmt.Print("Hello, World!\n")
}

The standard formatter

The philosophy of the Go design demands following some strict rules in how you design and format the program code. One of them, there should be no unused things in the program, such as variables or imported packages. Another thing is that the distribution includes a formatter that dictates how the program source should look like. Let us run our Hello, World! program against the formatter:

$ gofmt hello-world.go 

It prints the beautified version of the program, and the main things to note is that it forces the tab-based indentation:

package main

import "fmt"

func main() {
        fmt.Print("Hello, World!\n")
}

You may like it or not, but this is a requirement of the language intended to achieve the command standards among developers. But nobody stops you from setting a 4-space tab width in your editor.

Variables

There are two ways of declaring and initialising variables. In the first method, you use the var keyword:

var name, age = "John", 21
fmt.Printf("%s is %d.\n", name, age)

You can additionally add a type specifier:

var name string = "John"
var age int = 21

In the second method, no keywords are needed, but you use the := operator to declare and initialise a variable (the type is automatically inferred by the compiler):

name := "John"
age := 21

Constants

Constants are created with the const keyword and are set with the = operator only.

const name = "John"
// or:
// const name string = "John"

Functions

We have already seen how to use a function that takes no arguments and returns no value. In a general case, function declarations can be quite complex, for example: int (*(*fp)(int (*)(int, int), int))(int, int). Let us look at a few simpler and more commonly used cases.

You define a function with the func keyword and list the arguments and their types in parentheses. Then, you announce the return type:

package main

import "fmt"

func greet(name string) string {
    return "Hello, " + name + "!"
}

func main() {
    fmt.Println(greet("John"))
}

Function return options

Go functions can return multiple values (just list them comma-separated). You can simplify preparing the result by using the named multiple values, which is an interesting feature of the language. Consider the following example.

package main

import (
    "fmt"
    "strings"
)

func updown(s string) (hi, lo string) {
    hi = strings.ToUpper(s)
    lo = strings.ToLower(s)
    return
}

func main() {
    a, b := updown("Hello")
    fmt.Println(a) // HELLO
    fmt.Println(b) // hello
}

Inside the updown function, we are assigning the values to the hi and lo variables. They both are returned by a single return. The receiving code (a, b := in the main function) gets the two values in the order mentioned in the function declaration.

Also notice how you can import more than one package with a single import instruction.

A Factorial example

Here is a possible implementation of a function computing a factorial. This time, it is implemented with a loop.

package main

import "fmt"

func factorial(n int) int {
    result := 1
    for i := n; i != 0; i-- {
        result *= i
    }

    return result
}

func main() {
    fmt.Println(factorial(1)) // 1
    fmt.Println(factorial(5)) // 120
    fmt.Println(factorial(7)) // 5040
}

Notice how the loop variable is initialised with the :=, as it introduces a new variable.

Closures

A function can return another function. In the following example, we use this possibility to create two different ‘calculators’ that compute the second and the third power of a floating-point number. The goal of this example is to demonstrate how you define the return type of such a function.

package main

import (
    "fmt"
    "math"
)

func pow(pwr float64) func(float64) float64 {
    return func(n float64) float64 {
        return math.Pow(n, pwr)
    }
}

func main() {
    pow2 := pow(2)
    pow3 := pow(3)

    fmt.Println(pow2(5)) // 25
    fmt.Println(pow3(5)) // 125
}

Structs and methods

Object-oriented programming in Go is emulated via a combination of structures and method functions.

A structure is a compound construction declared with the struct keyword:

type Person struct {
    name string
    age int
}

You can create an object of this type by invoking its ‘constructor’:

p := Person{name: "Alla", age: 30}
fmt.Println(p) // {Alla 30}

To have the traditional object-oriented syntax, Go offers a special syntax for creating methods. You need to add a receiver:

func (p Person) info() {
    fmt.Printf("%s is %d.\n", p.name, p.age)
}

Now, you can use a dot to call this function:

p.info() // Alla is 30.

Such methods can take their own arguments as a regular function does it.

A Polymorphic example

Here is a polymorphic animal zoo example that uses the above-described structs and methods together with interfaces that allow us to define a common method name for more than one data type. Note that the program keeps the items in the array, whose type is []Animal, where Animal is an interface.

package main

import "fmt"

type Animal interface {
    info() string
}

type Cat struct {
    name string
}

type Dog struct {
    name string
}

func (c Cat) info() string {
    return c.name + " is a cat."
}

func (d Dog) info() string {
    return d.name + " is a dog."
}

func main() {
    zoo := []Animal{Dog{name: "Charlie"}, Cat{name: "Molly"}}

    for _, x := range zoo {
        fmt.Println(x.info())
    }
}

When you run the program, you will see that the x.info() calls are correctly dispatched to the corresponding functions: either func (c Cat) info() or func (d Dog) info():

Charlie is a dog.
Molly is a cat.

Concurrency

There is a magical word in Go, go, which allows us to execute a function as a goroutine.

Another magical things are channels that allow you to send a message to a channel and read it from there. You can do those actions in different functions or even in different goroutines.

For example, to make a channel for transporting integer numbers, create it like this:

ch := make(chan int)

Sleep sort

My Sleep sort implementation in Go uses both goroutines and channels to demonstrate their usage.

package main

import (
   "fmt"
   "time"
)

func sort_me(ch chan int, n int) {
   time.Sleep(time.Duration(n * 100) * time.Millisecond)
   ch <- n
}

func main() {
   data := []int{10, 4, 2, 6, 2, 7, 1, 3}

   sort_channel := make(chan int, len(data))
   for _, n := range data {
       go sort_me(sort_channel, n)
   }

   for i := 0; i != len(data); i++ {
       fmt.Println(<- sort_channel)
   }
}

In this program, we call go sort_me(...) for each number from the data array. The function receives the number and the channel that it uses to send data after the delay. Working with a channel is straightforward: to send data to it, use ->, to receive data, use <-. Note that you even don’t have to save it in a variable: fmt.Println(<- sort_channel).

Get more

Go is a mature language, so you will find a lot of online and offline resources to learn it. If you never used go, I would recommend starting from the online tour:

The source codes of the examples in this article are located on GitHub.

Next: Day 18. Hy

Leave a Reply

Your email address will not be published. Required fields are marked *

Retype the CAPTCHA code from the image
Change the CAPTCHA codeSpeak the CAPTCHA code