Blog

I think I like try

Yesterday Robert Griesemer posted a proposal for a new try built-in in Go. I have read the design document twice now and I think I like it.

I am not entirely comfortable saying that.

Two years ago I wrote that explicit error handling was exactly one of the things I loved about Go. I still do. The interesting question here is whether returning an error unchanged really counts as handling it.

The proposal turns this:

f, err := os.Open(filename)
if err != nil {
	return nil, err
}

into this:

f := try(os.Open(filename))

If os.Open returns an error, try places it in the enclosing function’s error result and returns from that function. Otherwise it gives us the other result. Errors remain values, functions still say that they return errors, and there are no exceptions jumping across some unknowable number of stack frames.

That is the part I like. A surprising amount of Go code is not really handling an error at all. It is checking the error and immediately passing it to the caller. I love explicit error handling, but writing four lines does not make that decision four times more explicit.

And yet try feels wrong.

It looks like a function but it can return from the function that called it. That is not something ordinary Go functions can do. The control flow is hidden inside an expression, and Go has spent the last ten years making control flow painfully, wonderfully obvious. There is no return on the line. It just returns.

The proposed way to add context also takes some getting used to. Instead of wrapping each error where it happens, a function can use a deferred handler to decorate the returned error. Using the helper sketched in the design, it would look something like this:

func readConfig(name string) (cfg Config, err error) {
	defer fmt.HandleErrorf(&err, "read configuration %s", name)

	data := try(ioutil.ReadFile(name))
	try(json.Unmarshal(data, &cfg))
	return cfg, nil
}

I am not sure I love the named result or the indirection in the handler. But I do like being able to see the actual operation without each line being separated from the next by the same little error-return ceremony.

My guess is that this proposal will not pass. It is a small addition in terms of specification and implementation, but a radical change in how Go code reads. Those are often the hardest changes: technically tiny and culturally enormous.

I also suspect that, if it does not pass, we may look back several years from now and wonder why it seemed so frightening. New syntax always has the disadvantage of looking unfamiliar in every example. The existing syntax has had a decade to become invisible.

Maybe I am wrong and try will turn real programs into a collection of secret exits. But the idea behind it is sound: keep explicit errors, while admitting that forwarding an error is not the interesting part of error handling.

That seems worth trying.

Zero values in Go and Lazy Initialization

I’m a big fan of the way Go does zero values, meaning it initializes every variable to a default value. This is in contrast with the way other languages such as, say, C behave. For instance, the printed result of the following C program is unpredictable.

#include <stdio.h>

int main(void) {
    int i;
    printf("%d\n", i);
    return 0;

}

The value of i will be whatever happens to be at the position in memory where the compiler happened to allocate the variable. Contrast this with the equivalent program in Go —

package main

import "fmt"

func main() {
    var i int
    fmt.Println(i)
}

This will always print `` because i is initialized by the compiler to the default value of an int, which happens to be 0.

This happens for every variable of any type, including our own custom types. What’s even cooler is that this is done recursively, so if you the fields inside a struct will also be themselves initialized.

I strive to make all my zero values useful, but it’s not always that simple. Sometimes you need to use different default values for your fields, or maybe you need to initialize one of those fields. This is especially important when we remember that the zero value of a pointer is nil.

Imagine the following type —

type Foobar struct {
    db *DB
}

func NewFoobar() *Foobar {
    return &Foobar{db: DB.New()}
}

func (f *Foobar) Get(key string) (*Foo, error) {
    foo, err := db.Get(key)
    if err != nil {
        return nil, err
    }
    return foo, nil
}

In the example above, our zero value is no longer useful: we’d cause a runtime error because db will be nil inside the Get() function. We’re forced to call NewFoobar() before using our functions.

But there’s a simple trick to make the Foobar zero value useful again. As it turns out, being lazy sometimes pays off. Our technique is called lazy initialization

type Foobar struct {
    dbOnce sync.Once
    db *DB
}

// lazy initialize db
func (f *Foobar) lazyInit() {
    f.dbOnce.Do(func() {
        f.db = DB.New()
    })
}

We added a sync.Once to our type. From the Go docs:

Once an object that will perform exactly one action.

The function we pass to sync.Once.Do() is guaranteed to run once and only once, so it is perfect for initializations. Now we can call lazyInit() at the top of our exported function and it will ensure db is initialized —

func (f *Foobar) Get(key string) (*Foo, error) {
    f.lazyInit()

    foo, err := db.Get(key)
    if err != nil {
        return nil, err
    }
    return foo, nil
}

...

var f Foobar
foo, err := f.Get("baz")

We are now free to use our zero value with no additional initialization. I love it.

Of course, it is not always possible to use zero values. For example, our Foobar assumes a magical object DB that can be initialized by itself, but in real life we probably need to connect to an external database, authenticate, etc and then pass the created DB to our Foobar.

Still, using lazy initialization allows us to make a lot of objects’ zero values useful that would otherwise not be.

Playing with Go module proxies

(This article has been graciously translated to Russian here. Huge thanks to Akhmad Karimov.)

I wrote a brief introduction to Go modules and in it I talked briefly about Go modules proxies and now that Go 1.11 is out, I thought I’d play a bit these proxies to figure our how they’re supposed to work.

Why

One of the goals of Go modules is to provide reproducible builds and it does a very good job by fetching the correct and expected files from a repository.

But what if the servers are offline? What if the repository simply vanishes?

One way teams deal with these risks is by vendoring the dependencies, which is fine. But Go modules offers another way: the use of a module proxy.

The Download Protocol

When Go modules support is enabled and the go command determines that it needs a module, it first looks at the local cache (under $GOPATH/pkg/mods). If it can’t find the right files there, it then goes ahead and fetches the files from the network (i.e. from a remote repo hosted on Github, Gitlab, etc.)

If we want to control what files go can download, we need to tell it to go through our proxy by setting the GOPROXY environment variable to point to our proxy’s URL. For instance:

export GOPROXY=http://gproxy.mycompany.local:8080

The proxy is nothing but a web server that responds to the module download protocol, which is a very simple API to query and fetch modules. The web server may even serve static files.

A typical scenario would be the go command trying to fetch github.com/pkg/errors:

The first thing go will do is ask the proxy for a list of available versions. It does this by making a GET request to /{module name}/@v/list. The server then responds with a simple list of versions it has available:

v0.8.0
v0.7.1

The go will determine which version it wants to download — the latest unless explicitly told otherwise1. It will then request information about that given version by issuing a GET request to /{module name}/@v/{module revision} to which the server will reply with a JSON representation of the struct:

type RevInfo struct {
    Version string    // version string
    Name    string    // complete ID in underlying repository
    Short   string    // shortened ID, for use in pseudo-version
    Time    time.Time // commit time
}

So for instance, we might get something like this:

{
    "Version": "v0.8.0",
    "Name": "v0.8.0",
    "Short": "v0.8.0",
    "Time": "2018-08-27T08:54:46.436183-04:00"
}

The go command will then request the module’s go.mod file by making a GET request to /{module name}/@v/{module revision}.mod. The server will simply respond with the contents of the go.mod file (e.g. module github.com/pkg/errors.) This file may list additional dependencies and the cycle restarts for each one.

Finally, the go command will request the actual module by getting /{module name}/@v/{module revision}.zip. The server should respond with a byte blob (application/zip) containing a zip archive with the module files where each file must be prefixed by the full module path and version (e.g. github.com/pkg/errors@v0.8.0/), i.e. the archive should contain:

github.com/pkg/errors@v0.8.0/example_test.go
github.com/pkg/errors@v0.8.0/errors_test.go
github.com/pkg/errors@v0.8.0/LICENSE
...

And not:

errors/example_test.go
errors/errors_test.go
errors/LICENSE
...

This seems like a lot when written like this, but it’s in fact a very simple protocol that simply fetches 3 or 4 files:

  1. The list of versions (only if go does not already know which version it wants)
  2. The module metadata
  3. The go.mod file
  4. The module zip itself

Creating a simple local proxy

To try out the proxy support, let’s create a very basic proxy that will serve static files from a directory. First we create a directory where we will store our in-site copies of our dependencies. Here’s what I have in mine:

$ find . -type f
./github.com/robteix/testmod/@v/v1.0.0.mod
./github.com/robteix/testmod/@v/v1.0.1.mod
./github.com/robteix/testmod/@v/v1.0.1.zip
./github.com/robteix/testmod/@v/v1.0.0.zip
./github.com/robteix/testmod/@v/v1.0.0.info
./github.com/robteix/testmod/@v/v1.0.1.info
./github.com/robteix/testmod/@v/list

 

These are the files our proxy will serve. You can find these files on Github if you’d like to play along. For the examples below, let’s assume we have a devel directory under our home directory; adapt accordingly.

$ cd $HOME/devel
$ git clone https://github.com/robteix/go-proxy-blog.git

Our proxy server is simple (it could be even simpler, but I wanted to log the requests):

package main

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

func main() {
    addr := flag.String("http", ":8080", "address to bind to")
    flag.Parse()

    dir := "."
    if flag.NArg() > 0 {
        dir = flag.Arg(0)
    }

    log.Printf("Serving files from %s on %s\n", dir, *addr)

    h := handler{http.FileServer(http.Dir(dir))}

    panic(http.ListenAndServe(*addr, h))
}

type handler struct {
    h http.Handler
}

func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    log.Println("New request:", r.URL.Path)
    h.h.ServeHTTP(w, r)
}

Now run the code above:

$ go run proxy.go -http :8080 $HOME/devel/go-proxy-blog
2018/08/29 14:14:31 Serving files from /home/robteix/devel/go-proxy-blog on :8080

$ curl http://localhost:8080/github.com/robteix/testmod/@v/list
v1.0.0
v1.0.1

Leave the proxy running and move to a new terminal. Now let’s create a new test program. we create a new directory $HOME/devel/test and create a file named test.go inside it with the following code:

package main

import (
    "github.com/robteix/testmod"
)

func main() {
    testmod.Hi("world")
}

And now, inside this directory, let’s enable Go modules:

$ go mod init test

And we set the GOPROXY variable:

export GOPROXY=http://localhost:8080

Now let’s try building our new program:

$ go build
go: finding github.com/robteix/testmod v1.0.1
go: downloading github.com/robteix/testmod v1.0.1

And if you check the output from our proxy:

2018/08/29 14:56:14 New request: /github.com/robteix/testmod/@v/list
2018/08/29 14:56:14 New request: /github.com/robteix/testmod/@v/v1.0.1.info
2018/08/29 14:56:14 New request: /github.com/robteix/testmod/@v/v1.0.1.mod
2018/08/29 14:56:14 New request: /github.com/robteix/testmod/@v/v1.0.1.zip

So as long as GOPROXY is set, go will only download files from out proxy. If I go ahead and delete the repository from Github, things will continue to work.

Using a local directory

It is interesting to note that we don’t even need our proxy.go at all. We can set GOPROXY to point to a directory in the filesystem and things will still work as expected:

export GOPROXY=file://home/robteix/devel/go-proxy-blog

If we do a go build now2, we’ll see exactly the same thing as with the proxy:

$ go build
go: finding github.com/robteix/testmod v1.0.1
go: downloading github.com/robteix/testmod v1.0.1

Of course, in real life, we probably will prefer to have a company/team proxy server where our dependencies are stored, because a local directory is not really much different from the local cache that go already maintains under $GOPATH/pkg/mod, but still, nice to know that it works.

There is a project called Athens that is building a proxy and that aims — if I don’t misunderstand it — to create a central repository of packages à la npm.

  • Remember that somepackage and somepackage/v2 are treated as different packages. 
  • That’s not strictly true as now that we’ve already built it once, go has cached the module locally and will not go to the proxy (or the network) at all. You can still force it by deleting $GOPATH/pkg/mod/cache/download/github.com/robteix/testmod/ and $GOPATH/pkg/mod/github.com/robteix/testmod@v1.0.1
  • Introduction to Go Modules

    This post is also available in other languages:

    The upcoming version 1.11 of the Go programming language will bring experimental support for modules, a new dependency management system for Go. A few days ago, I wrote a quick post about it. Since that post went live, things changed a bit and as we’re now very close to the new release, I thought it would be a good time for another post with a more hands-on approach. So here’s what we’ll do: we’ll create a new package and then we’ll make a few releases to see how that would work.

    Creating a Module

    So first things first. Let’s create our package. We’ll call it “testmod”. An important detail here: **this directory should be **outside your $GOPATH because by default, the modules support is disabled inside it. Go modules is a first step in potentially eliminating $GOPATH entirely at some point.

    $ mkdir testmod
    $ cd testmod
    

    Our package is very simple:

    package testmod
    
    import "fmt"
    
    // Hi returns a friendly greeting
    func Hi(name string) string {
       return fmt.Sprintf("Hi, %s", name)
    }
    

    The package is done but it is still not a module. Let’s change that.

    $ go mod init github.com/robteix/testmod
    go: creating new go.mod: module github.com/robteix/testmod
    

    This creates a new file named go.mod in the package directory with the following contents:

    module github.com/robteix/testmod
    

    Not a lot here, but this effectively turns our package into a module. We can now push this code to a repository:

    $ git init
    $ git add *
    $ git commit -am "First commit"
    $ git push -u origin master
    

    Until now, anyone willing to use this package would go get it:

    $ go get github.com/robteix/testmod
    

    And this would fetch the latest code in master. This still works, but we should probably stop doing that now that we have a Better Way™. Fetching master is inherently dangerous as we can never know for sure that the package authors didn’t make change that will break our usage. That’s what modules aims at fixing.

    Quick Intro to Module Versioning

    Go modules are versioned, and there are some particularities with regards to certain versions. You will need to familiarize yourself with the concepts behind semantic versioning.

    More importantly, Go will use repository tags when looking for versions, and some versions are different of others: e.g. versions 2 and greater should have a different import path than versions 0 and 1 (we’ll get to that.) As well, by default Go will fetch the latest tagged version available in a repository. This is an important gotcha as you may be used to working with the master branch. What you need to keep in mind for now is that to make a release of our package, we need to tag our repository with the version. So let’s do that.

    Making our first release

    Now that our package is ready, we can release it to the world. We do this by using version tags. Let’s release our version 1.0.0:

    $ git tag v1.0.0
    $ git push --tags
    

    This creates a tag on my Github repository marking the current commit as being the release 1.0.0.

    Go doesn’t enforce that in any way, but a good idea is to also create a new branch (“v1”) so that we can push bug fixes to.

    $ git checkout -b v1
    $ git push -u origin v1
    

    Now we can work on master without having to worry about breaking our release.

    Using our module

    Now we’re ready to use the module. We’ll create a simple program that will use our new package:

    package main
    
    import (
    	"fmt"
    
    	"github.com/robteix/testmod"
    )
    
    func main() {
    	fmt.Println(testmod.Hi("roberto"))
    }
    

    Until now, you would do a go get github.com/robteix/testmod to download the package, but with modules, this gets more interesting. First we need to enable modules in our new program.

    $ go mod init mod
    

    As you’d expect from what we’ve seen above, this will have created a new go.mod file with the module name in it:

    module mod
    

    Things get much more interesting when we try to build our new program:

    $ go build
    go: finding github.com/robteix/testmod v1.0.0
    go: downloading github.com/robteix/testmod v1.0.0
    

    As we can see, the go command automatically goes and fetches the packages imported by the program. If we check our go.mod file, we see that things have changed:

    module mod
    require github.com/robteix/testmod v1.0.0
    

    And we now have a new file too, named go.sum, which contains hashes of the packages, to ensure that we have the correct version and files.

    github.com/robteix/testmod v1.0.0 h1:9EdH0EArQ/rkpss9Tj8gUnwx3w5p0jkzJrd5tRAhxnA=
    github.com/robteix/testmod v1.0.0/go.mod h1:UVhi5McON9ZLc5kl5iN2bTXlL6ylcxE9VInV71RrlO8=
    

    Making a bugfix release

    Now let’s say we realized a problem with our package: the greeting is missing ponctuation! People are mad because our friendly greeting is not friendly enough. So we’ll fix it and release a new version:

    // Hi returns a friendly greeting
    func Hi(name string) string {
    -       return fmt.Sprintf("Hi, %s", name)
    +       return fmt.Sprintf("Hi, %s!", name)
    }
    

    We made this change in the v1 branch because it’s not relevant for what we’ll do for v2 later, but in real life, maybe you’d do it in master and then back-port it. Either way, we need to have the fix in our v1 branch and mark it as a new release.

    $ git commit -m "Emphasize our friendliness" testmod.go
    $ git tag v1.0.1
    $ git push --tags origin v1
    

    Updating modules

    By default, Go will not update modules without being asked. This is a Good Thing™ as we want predictability in our builds. If Go modules were automatically updated every time a new version came out, we’d be back in the uncivilized age pre-Go1.11. No, we need to tell Go to update a modules for us.

    We do this by using our good old friend go get:

    • run go get -u to use the latest minor or patch releases (i.e. it would update from 1.0.0 to, say, 1.0.1 or, if available, 1.1.0)
    • run go get -u=patch to use the latest patch releases (i.e., would update to 1.0.1 but not to 1.1.0)
    • run go get package@version to update to a specific version (say, github.com/robteix/testmod@v1.0.1)

    In the list above, there doesn’t seem to be a way to update to the latest major version. There’s a good reason for that, as we’ll see in a bit.

    Since our program was using version 1.0.0 of our package and we just created version 1.0.1, any of the following commands will update us to 1.0.1:

    $ go get -u
    $ go get -u=patch
    $ go get github.com/robteix/testmod@v1.0.1
    

    After running, say, go get -u our go.mod is changed to:

    module mod
    require github.com/robteix/testmod v1.0.1
    

    Major versions

    According to semantic version semantics, a major version is different than minors. Major versions can break backwards compatibility. From the point of view of Go modules, a major version is a different package completely. This may sound bizarre at first, but it makes sense: two versions of a library that are not compatible with each other are two different libraries.

    Let’s make a major change in our package, shall we? Over time, we realized our API was too simple, too limited for the use cases of our users, so we need to change the Hi() function to take a new parameter for the greeting language:

    package testmod
    
    import (
    	"errors"
    	"fmt"
    )
    
    // Hi returns a friendly greeting in language lang
    func Hi(name, lang string) (string, error) {
    	switch lang {
    	case "en":
    		return fmt.Sprintf("Hi, %s!", name), nil
    	case "pt":
    		return fmt.Sprintf("Oi, %s!", name), nil
    	case "es":
    		return fmt.Sprintf("¡Hola, %s!", name), nil
    	case "fr":
    		return fmt.Sprintf("Bonjour, %s!", name), nil
    	default:
    		return "", errors.New("unknown language")
    	}
    }
    

    Existing software using our API will break because they (a) don’t pass a language parameter and (b) don’t expect an error return. Our new API is no longer compatible with version 1.x so it’s time to bump the version to 2.0.0.

    I mentioned before that some versions have some peculiarities, and this is the case now. **Versions 2 **and over should change the import path. They are different libraries now.

    We do this by appending a new version path to the end of our module name.

    module github.com/robteix/testmod/v2
    

    The rest is the same as before, we push it, tag it as v2.0.0 (and optionally create a v2 branch.)

    $ git commit testmod.go -m "Change Hi to allow multilang"
    $ git checkout -b v2 # optional but recommended
    $ echo "module github.com/robteix/testmod/v2" > go.mod
    $ git commit go.mod -m "Bump version to v2"
    $ git tag v2.0.0
    $ git push --tags origin v2 # or master if we don't have a branch
    

    Updating to a major version

    Even though we have released a new incompatible version of our library, existing software will not break, because it will continue to use the existing version 1.0.1. go get -u will not get version 2.0.0.

    At some point, however, I, as the library user, may want to upgrade to version 2.0.0 because maybe I was one of those users who needed multi-language support.

    I do it but modifying my program accordingly:

    package main
    
    import (
    	"fmt"
    	"github.com/robteix/testmod/v2"
    )
    
    func main() {
    	g, err := testmod.Hi("Roberto", "pt")
    	if err != nil {
    		panic(err)
    	}
    	fmt.Println(g)
    }
    

    And then when I run go build, it will go and fetch version 2.0.0 for me. Notice how even though the import path ends with “v2”, Go will still refer to the module by its proper name (“testmod”).

    As I mentioned before, the major version is for all intents and purposes a completely different package. Go modules does not link the two at all. That means we can use two incompatible versions in the same binary:

    package main
    import (
    	"fmt"
    	"github.com/robteix/testmod"
    	testmodML "github.com/robteix/testmod/v2"
    )
    
    func main() {
    	fmt.Println(testmod.Hi("Roberto"))
    	g, err := testmodML.Hi("Roberto", "pt")
    	if err != nil {
    		panic(err)
    	}
    	fmt.Println(g)
    }
    

    This eliminates a common problem with dependency management: when dependencies depend on different versions of the same library.

    Tidying it up

    Going back to the previous version that uses only testmod 2.0.0, if we check the contents of go.mod now, we’ll notice something:

    module mod
    require github.com/robteix/testmod v1.0.1
    require github.com/robteix/testmod/v2 v2.0.0
    

    By default, Go does not remove a dependency from go.mod unless you ask it to. If you have dependencies that you no longer use and want to clean up, you can use the new tidy command:

    $ go mod tidy
    

    Now we’re left with only the dependencies that are really being used.

    Vendoring

    Go modules ignores the vendor/ directory by default. The idea is to eventually do away with vendoring[^0]. But if we still want to add vendored dependencies to our version control, we can still do it:

    $ go mod vendor
    

    This will create a vendor/ directory under the root of your project containing the source code for all of your dependencies.

    Still, go build will ignore the contents of this directory by default. If you want to build dependencies from the vendor/ directory, you’ll need to ask for it.

    $ go build -mod vendor
    

    I expect many developers willing to use vendoring will run go build normally on their development machines and use -mod vendor in their CI.

    Again, Go modules is moving away from the idea of vendoring and towards using a Go module proxy for those who don’t want to depend on the upstream version control services directly.

    There are ways to guarantee that go will not reach the network at all (e.g. GOPROXY=off) but these are the subject for a future blog post.

    Conclusion

    This post may seem a bit daunting, but I tried to explain a lot of things together. The reality is that now Go modules is basically transparent. We import package like always in our code and the go command will take care of the rest.

    When we build something, the dependencies will be fetched automatically. It also eliminates the need to use $GOPATH which was a roadblock for new Go developers who had trouble understanding why things had to go into a specific directory.

    Vendoring is (unofficially) being deprecated in favour of using proxies.[^0] I may do a separate post about the Go module proxy. (Update: it’s live.) [^0]: I think this came out a bit too strong and people left with the impression that vendoring is being removed right now. It isn’t. Vendoring still works, albeit slightly different than before. There seems to be a desire to replace vendoring with something better, which may or may not be a proxy. But for now this is just it: a desire for a better solution. Vendoring is not going away until a good replacement is found (if ever.)

    New licence plate

    Update: the licence plate application has since been refused. The reason given is that they don’t allow offensive messages. All I can think of is that they misread it as being “GOP HER,” which doesn’t mean anything but they may have assumed it was some code, new slang or something.

    I live in Quebec and only recently the province opened registrations for personalized license plates. At first I didn’t even consider it, but this morning I impulse-bought one:

    This will of course be a homage to my favourite mascot, Go’s.