- Sauvik Biswas - https://sauvikbiswas.com -

Learning Go: A preliminary assessment of the language

To be updated by Sauvik from the future who I presume would be far more fluent in the language. — SB. 06.06.2021

In the last 4 days, I have gone through the introductory tutorials of Go. They comprise of the following—Tutorial: Getting started [1], Tutorial: Create a module [2], and Writing Web Applications [3]. I found it to be enough to give a flavour of the language but not enough to start writing proper applications. I found the language to be a hybrid of languages like C and Python. I understood that the language was built to ease the developer’s effort to write concurrent programs.

Here are some of the things I learnt (I might be wrong but I’ll rely on future Sauvik to correct this post)—

1. Type plays a significant role in Go

There are built-in types and there are custom types. The custom types are like structs in C.

type <type-name> struct {
    <field-1> <field-1 type>
    <field-2> <field-2 type>
    ...       
}

Variables of this type can be constructed on the fly. If some field is not specified, it is set to nil.

<type-name>{<field-1>: <value-1>, <field-2>: <value-2>, ...}

Almost all function definitions require the types of the arguments and return. This is very much like C and not like Python where type is inferred. (Although type declaration is the preferred way of writing functions in Python 3.)

func <function-name> (<arg-1> <arg-1 type>, <arg-2> <arg-2 type>, ...) (<return-1 type>, <return-2 type>, ...) {
    ...
}

Types can be augmented with functions. The structure is very similar to how methods are written for classes. In Go they are called receivers. I do not know why it is named so.

func (<receiver> <receiver-type>) <function-name> (<arg-1> <arg-1 type>, ...) (<return-1 type>, ...) {
    ...
}

Based on my preliminary understanding, this mechanism is useful when the contents of a struct has to be modified (just like a mutating method in a class) or when the contents have to be transmitted via some I/O (eg. save, post, etc.)

It is possible to infer a type during assignment by using the := operator. It is also a common practice. This is the only case so far where I have not seen an explicit type assignment.

<variable> := <expression>

is equivalent to

var <variable> <type>
<variable> = <expression>

There are a couple of familiar compound built-in types. Slices and Maps are equivalent to dynamic lists and assoc arrays/hash table/dictionary respectively.

// Slice
[]<type>{<value-1>, <value-2>, <value-3>, ...}
// Map
<variable> := make(map[<key-type>]<value-type>)
<variable>[<key-1>] = <value-1>
...

The statement <variable>[<key-1>] = <value-1> doesn’t need the := operator as the type is already declared for the map. The make keyword can do some operation with arrays, too, but I do not know what it does.

I also found instances of typecasting. I do not know the mechanism how it is implemented. I believe there will be an analogue with Python where the casts’ behaviour as well as operators’ behaviour can be expressed for a given type.

<new-type>(<value of old type>) // cast
[]byte("This is a string.") // example

2. Pointers and global variables

The pointer implementation appears to be very similar to that in C. This includes the syntax as well. I must admit that this assessment is superficial.

// Store a pointer to a struct
<variable> := &<type-name>{<field-1>: <value-1>, <field-2>: <value-2>, ...}

// Declare the type that can use this pointer as argument
func <function name>(<arg-1> *<type-name>, ...) ...

It appears that this is similar to pass by value and pass by reference implemented in C.

Pointers are not the only way to mutate the value of a variable between functions. Global variables can also be used to achieve a similar effect.

var <variable> = <expression>

That code has to be written outside a function. I am not sure why the type declaration for this variable is not required.

3. Go seems to be designed to be web first.

This aspect is not a surprise as the preliminary motivation for the trio (Griesemer, Pike and Thomson) was to achieve ease of coding concurrency for web infrastructure at Google.

Most of the functions are embedded in net/http [4] package. The following piece of code succinctly demonstrates some of the aspects.

package main

import (
    "fmt"
    "log"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

First of all, it is super easy to instantiate a web-server using http.ListenAndServe(addr String, handler Handler). I am not sure what the handler does here in the syntax. But in the above code setting it to nil seems to work. Also, I like how simply the output can be piped to a log. In this case only the Fatal messages would be printed to log file pointer, which is stdout by default.

The entry points are defined by http.HandleFunc(pattern string, handler func(ResponseWriter, *Request)). The string pattern is the pattern that is matched in the URL (or is it any generic GET request?) and the function of type func(ResponseWriter, *Request) is called. The ResponseWriter is similar to file pointer used by many languages; only in this case, it is the posting the data to the client. The only use of Request I have encountered so far is to get the URL from Request.URL.Path.

HTTP redirects and error handling is also done using this package.

http.Redirect(<http.ResponseWriter>, <*http.Request>, <redirect-URL>, http.StatusFound)

There are a number of predefined standard HTTP status codes. http.StatusFound will send a HTTP 302 status while the following sends a HTTP 500 status while also sending a error string as a body, which could be an HTML page that can be viewed by the end-user.

http.Error(<http.ResponseWriter>, <error-string>, http.StatusInternalServerError)

4. Go has a built-in HTML template packages

One of the helper package is html/template [5]. The idea appeared to be similar to Flask/Jinja templates [6] I have used in Python. The following example is used as a template.

<h1>{{.Title}}</h1>
<p>[<a href="/edit/{{.Title}}">edit</a>]</p>
<div>{{printf "%s" .Body}}</div>

When this template is rendered with a struct whose members are Title and Body, the {{ }} is replaced with the value. There is also a call to printf function which converts Body []byte to string.

Few questions remain unanswered at this point. Firstly, what happens when there is no member in a struct but the template is asked to replace it? Secondly, where is the printf function defined? In Go, the standard print functions are in fmt package (fmt.Printf, fmt.Println).

The call to reading and executing templates directly to the client is handled by two lines of code. The first one to parse the template and the second one to post the struct-rendered template directly to the client.

t, _ := template.ParseFiles(filename)
t.Execute(<http.ResponseWriter>, <struct>)

Template seems to be common thing in building some kind of a text-based output. There is an in-built caching functions for templates as well. It can be cached to a global variable.

var cachedtemplates = template.Must(template.ParseFiles(<template-file-1>, <template-file-2>, ...))

These can be later called to specifically render a template with a struct and post to the client.

err := cachedtemplates.ExecuteTemplate(<http.ResponseWriter>, <template-file-n>, <struct>)

The errors are caught and can be used to trigger specific HTTP response codes.

While experimenting, I also found that if the entire path is specified for ParseFiles, only the filename is used to name the template.

var cachedtemplates = template.Must(template.ParseFiles(<path/to/template-file-1>, <path/to/template-file-2>, ...))

Thus the call cachedtemplates.ExecuteTemplate the argument should still be <template-file-n> and not <path/to/template-file-n>.

5. Go emphises modules and packaging right from the start

It was unusual for me to see the opening tutorial focus on creating package and test suite to go with the package. A number of the languages design features rely on nomenclature to derive the nature of the object.

Methods of packages exposed to the importing code start with a capital letter. In a package called demo, if a function is declared as func FuncX(), the importer can call it using demo.FuncX() unlike a function defined as func funcY(). I am unsure if this is a convention or if it is strictly implemented. Python uses the convention that any method starting with an underscore is a private function. However, this is merely a convention as nothing stops the importing code to make a call to the function.

The first tutorial is called “Create a module [2]“. This is unlike most languages that I know. It is almost like you define the environment first and then start coding. In Go, a module is a collection of packages. Let’s take an import statement.

import "net/http"

In this case net is the module while http is a package inside net. I do not know how these would be constructed in a complex project with multiple code locations and not-so-straightforward directory tree.

Every file has an opening line that says which package it belongs to. The entry is via the file that says package main and has the func main() declaration. The latter is just like C.

Go module name, dependencies, local redirects and versioning are stored in a file called go.mod. It is recommended that it is created using the go mod init command.

go mod init <module-name>

From time to time, as the code evolves, the tidy procedure can clean the file up automatically

go mod tidy

The web-first nature of Go is also apparent here. go.mod file may have a line like this.

require <website>/<module>

This says that the module in the current directory is dependent on a remote module. If you are the developer of this module, pointing to a local copy is made simpler by introducing a replace line in the go.mod file.

replace <website>/<module> => <path/to/local/copy/module>

The testing system also relies on file nomenclature. Any file with the suffix _test.go are executed when go test is called. This is not something I have seen in other languages.

6. Function literals and closures

The ability to use functions as first class objects is like Python. However, the inclusion of type declaration reminds me of Haskell. If the following function is declared

func operate(fn func(int, int) int, a int, b int) int {
    return fn(a,b)
}

We can use any bivariate function that takes two int arguments and returns an int and use it as an argument to operate. Being a first class object, any function can be passed around or stored as variables.

P.S. I like Haskell’s declaration syntax. The above code would have the type declaration as (Int -> Int -> Int) -> Int -> Int -> Int.

Closures are handled just like in Python. I do not know if the closure variables can be accessed from outside.

What’s Next

That’s quite a lot of information for four days of study. Next, I would like to understand data types followed by Go’s implementation of concurrency.

I will also correct any errors in this post which has stemmed from my incomplete understanding of the language as I learn more.

Day 16: Back to Guwahati [7]