The Go Blog

Go.dev: a new hub for Go developers

Steve Francia and Julie Qiu
13 November 2019

Over the last two years, as we’ve spoken with users at companies of all sizes, we’ve heard three questions repeatedly: who else is using Go, what do they use it for, and how can I find useful Go packages?

Today we are launching go.dev, a new hub for Go developers, to help answer those questions. There you will find a wealth of learning resources to get started with the language, featured use cases, and case studies of companies using Go.

(Note that golang.org is still the home for the open source Go project and the Go distribution. Go.dev is a companion site to provide these supporting resources.)

Clicking on Explore brings you to pkg.go.dev, a central source of information about Go packages and modules. Like godoc.org, pkg.go.dev serves Go documentation. However, it also understands modules and has information about all versions of a package, including all releases of the standard library! And it detects and displays licenses and has a better search algorithm. You can follow Go issue 33654 for future developments.

Today’s launch is our minimum viable product for go.dev, so we can share what we’ve built to help the community and get feedback. We intend to expand the site over time. If you have any ideas, suggestions or issues, please let us know via the “Share Feedback” and “Report an Issue” links at the bottom of every page. Or you can send your bugs, ideas, feature requests, and questions to go-discovery-feedback@google.com.

Go Turns 10

Russ Cox, for the Go team
8 November 2019

Happy birthday, Go!

This weekend we celebrate the 10th anniversary of the Go release, marking the 10th birthday of Go as an open-source programming language and ecosystem for building modern networked software.

To mark the occasion, Renee French, the creator of the Go gopher, painted this delightful scene:

Celebrating 10 years of Go makes me think back to early November 2009, when we were getting ready to share Go with the world. We didn’t know what kind of reaction to expect, whether anyone would care about this little language. I hoped that even if no one ended up using Go, we would at least have drawn attention to some good ideas, especially Go’s approach to concurrency and interfaces, that could influence follow-on languages.

Once it became clear that people were excited about Go, I looked at the history of popular languages like C, C++, Perl, Python, and Ruby, examining how long each took to gain widespread adoption. For example, Perl seemed to me to have appeared fully-formed in the mid-to-late 1990s, with CGI scripts and the web, but it was first released in 1987. This pattern repeated for almost every language I looked at: it seems to take roughly a decade of quiet, steady improvement and dissemination before a new language really takes off.

I wondered: where would Go be after a decade?

Today, we can answer that question: Go is everywhere, used by at least a million developers worldwide.

Go’s original target was networked system infrastructure, what we now call cloud software. Every major cloud provider today uses core cloud infrastructure written in Go, such as Docker, Etcd, Istio, Kubernetes, Prometheus, and Terraform; the majority of the Cloud Native Computing Foundation’s projects are written in Go. Countless companies are using Go to move their own work to the cloud as well, from startups building from scratch to enterprises modernizing their software stack. Go has also found adoption well beyond its original cloud target, with uses ranging from controlling tiny embedded systems with GoBot and TinyGo to detecting cancer with massive big data analysis and machine learning at GRAIL, and everything in between.

All this is to say that Go has succeeded beyond our wildest dreams. And Go’s success isn’t just about the language. It’s about the language, the ecosystem, and especially the community working together.

In 2009, the language was a good idea with a working sketch of an implementation. The go command did not exist: we ran commands like 6g to compile and 6l to link binaries, automated with makefiles. We typed semicolons at the ends of statements. The entire program stopped during garbage collection, which then struggled to make good use of two cores. Go ran only on Linux and Mac, on 32- and 64-bit x86 and 32-bit ARM.

Over the last decade, with the help of Go developers all over the world, we have evolved this idea and sketch into a productive language with fantastic tooling, a production-quality implementation, a state-of-the-art garbage collector, and ports to 12 operating systems and 10 architectures.

Any programming language needs the support of a thriving ecosystem. The open source release was the seed for that ecosystem, but since then, many people have contributed their time and talent to fill the Go ecosystem with great tutorials, books, courses, blog posts, podcasts, tools, integrations, and of course reusable Go packages importable with go get. Go could never have succeeded without the support of this ecosystem.

Of course, the ecosystem needs the support of a thriving community. In 2019 there are dozens of Go conferences all over the world, along with over 150 Go meetup groups with over 90,000 members. GoBridge and Women Who Go help bring new voices into the Go community, through mentoring, training, and conference scholarships. This year alone, they have taught hundreds of people from traditionally underrepresented groups at workshops where community members teach and mentor those new to Go.

There are over a million Go developers worldwide, and companies all over the globe are looking to hire more. In fact, people often tell us that learning Go helped them get their first jobs in the tech industry. In the end, what we’re most proud of about Go is not a well-designed feature or a clever bit of code but the positive impact Go has had in so many people’s lives. We aimed to create a language that would help us be better developers, and we are thrilled that Go has helped so many others.

As #GoTurns10, I hope everyone will take a moment to celebrate the Go community and all we have achieved. On behalf of the entire Go team at Google, thank you to everyone who has joined us over the past decade. Let’s make the next one even more incredible!

Go Modules: v2 and Beyond

Jean de Klerk and Tyler Bui-Palsulich
7 November 2019

Introduction

This post is part 4 in a series.

As a successful project matures and new requirements are added, past features and design decisions might stop making sense. Developers may want to integrate lessons they've learned by removing deprecated functions, renaming types, or splitting complicated packages into manageable pieces. These kinds of changes require effort by downstream users to migrate their code to the new API, so they should not be made without careful consideration that the benefits outweigh the costs.

For projects that are still experimental — at major version v0 — occasional breaking changes are expected by users. For projects which are declared stable — at major version v1 or higher — breaking changes must be done in a new major version. This post explores major version semantics, how to create and publish a new major version, and how to maintain multiple major versions of a module.

Major versions and module paths

Modules formalized an important principle in Go, the import compatibility rule:

If an old package and a new package have the same import path,
the new package must be backwards compatible with the old package.

By definition, a new major version of a package is not backwards compatible with the previous version. This means a new major version of a module must have a different module path than the previous version. Starting with v2, the major version must appear at the end of the module path (declared in the module statement in the go.mod file). For example, when the authors of the module github.com/googleapis/gax-go developed v2, they used the new module path github.com/googleapis/gax-go/v2. Users who wanted to use v2 had to change their package imports and module requirements to github.com/googleapis/gax-go/v2.

The need for major version suffixes is one of the ways Go modules differs from most other dependency management systems. Suffixes are needed to solve the diamond dependency problem. Before Go modules, gopkg.in allowed package maintainers to follow what we now refer to as the import compatibility rule. With gopkg.in, if you depend on a package that imports gopkg.in/yaml.v1 and another package that imports gopkg.in/yaml.v2, there is no conflict because the two yaml packages have different import paths — they use a version suffix, as with Go modules. Since gopkg.in shares the same version suffix methodology as Go modules, the Go command accepts the .v2 in gopkg.in/yaml.v2 as a valid major version suffix. This is a special case for compatibility with gopkg.in: modules hosted at other domains need a slash suffix like /v2.

Major version strategies

The recommended strategy is to develop v2+ modules in a directory named after the major version suffix.

github.com/googleapis/gax-go @ master branch
/go.mod    → module github.com/googleapis/gax-go
/v2/go.mod → module github.com/googleapis/gax-go/v2

This approach is compatible with tools that aren't aware of modules: file paths within the repository match the paths expected by go get in GOPATH mode. This strategy also allows all major versions to be developed together in different directories.

Other strategies may keep major versions on separate branches. However, if v2+ source code is on the repository's default branch (usually master), tools that are not version-aware — including the go command in GOPATH mode — may not distinguish between major versions.

The examples in this post will follow the major version subdirectory strategy, since it provides the most compatibility. We recommend that module authors follow this strategy as long as they have users developing in GOPATH mode.

Publishing v2 and beyond

This post uses github.com/googleapis/gax-go as an example:

$ pwd
/tmp/gax-go
$ ls
CODE_OF_CONDUCT.md  call_option.go  internal
CONTRIBUTING.md     gax.go          invoke.go
LICENSE             go.mod          tools.go
README.md           go.sum          RELEASING.md
header.go
$ cat go.mod
module github.com/googleapis/gax-go

go 1.9

require (
    github.com/golang/protobuf v1.3.1
    golang.org/x/exp v0.0.0-20190221220918-438050ddec5e
    golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
    golang.org/x/tools v0.0.0-20190114222345-bf090417da8b
    google.golang.org/grpc v1.19.0
    honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099
)
$

To start development on v2 of github.com/googleapis/gax-go, we'll create a new v2/ directory and copy our package into it.

$ mkdir v2
$ cp *.go v2/
building file list ... done
call_option.go
gax.go
header.go
invoke.go
tools.go

sent 10588 bytes  received 130 bytes  21436.00 bytes/sec
total size is 10208  speedup is 0.95
$

Now, let's create a v2 go.mod file by copying the current go.mod file and adding a v2/ suffix to the module path:

$ cp go.mod v2/go.mod
$ go mod edit -module github.com/googleapis/gax-go/v2 v2/go.mod
$

Note that the v2 version is treated as a separate module from the v0 / v1 versions: both may coexist in the same build. So, if your v2+ module has multiple packages, you should update them to use the new /v2 import path: otherwise, your v2+ module will depend on your v0 / v1 module. For example, to update all github.com/my/project references to github.com/my/project/v2, you can use find and sed:

$ find . -type f \
    -name '*.go' \
    -exec sed -i -e 's,github.com/my/project,github.com/my/project/v2,g' {} \;
$

Now we have a v2 module, but we want to experiment and make changes before publishing a release. Until we release v2.0.0 (or any version without a pre-release suffix), we can develop and make breaking changes as we decide on the new API. If we want users to be able to experiment with the new API before we officially make it stable, we can publish a v2 pre-release version:

$ git tag v2.0.0-alpha1
$ git push origin v2.0.0-alpha1
$

Once we are happy with our v2 API and are sure we don't need any other breaking changes, we can tag v2.0.0:

$ git tag v2.0.0
$ git push origin v2.0.0
$

At that point, there are now two major versions to maintain. Backwards compatible changes and bug fixes will lead to new minor and patch releases (for example, v1.1.0, v2.0.1, etc.).

Conclusion

Major version changes result in development and maintenance overhead and require investment from downstream users to migrate. The larger the project, the larger these overheads tend to be. A major version change should only come after identifying a compelling reason. Once a compelling reason has been identified for a breaking change, we recommend developing multiple major versions in the master branch because it is compatible with a wider variety of existing tools.

Breaking changes to a v1+ module should always happen in a new, vN+1 module. When a new module is released, it means additional work for the maintainers and for the users who need to migrate to the new package. Maintainers should therefore validate their APIs before making a stable release, and consider carefully whether breaking changes are really necessary beyond v1.

Working with Errors in Go 1.13

Damien Neil and Jonathan Amsterdam
17 October 2019

Introduction

Go’s treatment of errors as values has served us well over the last decade. Although the standard library’s support for errors has been minimal—just the errors.New and fmt.Errorf functions, which produce errors that contain only a message—the built-in error interface allows Go programmers to add whatever information they desire. All it requires is a type that implements an Error method:

type QueryError struct {
    Query string
    Err   error
}

func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }

Error types like this one are ubiquitous, and the information they store varies widely, from timestamps to filenames to server addresses. Often, that information includes another, lower-level error to provide additional context.

The pattern of one error containing another is so pervasive in Go code that, after extensive discussion, Go 1.13 added explicit support for it. This post describes the additions to the standard library that provide that support: three new functions in the errors package, and a new formatting verb for fmt.Errorf.

Before describing the changes in detail, let's review how errors are examined and constructed in previous versions of the language.

Errors before Go 1.13

Examining errors

Go errors are values. Programs make decisions based on those values in a few ways. The most common is to compare an error to nil to see if an operation failed.

if err != nil {
    // something went wrong
}

Sometimes we compare an error to a known sentinel value, to see if a specific error has occurred.

var ErrNotFound = errors.New("not found")

if err == ErrNotFound {
    // something wasn't found
}

An error value may be of any type which satisfies the language-defined error interface. A program can use a type assertion or type switch to view an error value as a more specific type.

type NotFoundError struct {
    Name string
}

func (e *NotFoundError) Error() string { return e.Name + ": not found" }

if e, ok := err.(*NotFoundError); ok {
    // e.Name wasn't found
}

Adding information

Frequently a function passes an error up the call stack while adding information to it, like a brief description of what was happening when the error occurred. A simple way to do this is to construct a new error that includes the text of the previous one:

if err != nil {
    return fmt.Errorf("decompress %v: %v", name, err)
}

Creating a new error with fmt.Errorf discards everything from the original error except the text. As we saw above with QueryError, we may sometimes want to define a new error type that contains the underlying error, preserving it for inspection by code. Here is QueryError again:

type QueryError struct {
    Query string
    Err   error
}

Programs can look inside a *QueryError value to make decisions based on the underlying error. You'll sometimes see this referred to as "unwrapping" the error.

if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
    // query failed because of a permission problem
}

The os.PathError type in the standard library is another example of one error which contains another.

Errors in Go 1.13

The Unwrap method

Go 1.13 introduces new features to the errors and fmt standard library packages to simplify working with errors that contain other errors. The most significant of these is a convention rather than a change: an error which contains another may implement an Unwrap method returning the underlying error. If e1.Unwrap() returns e2, then we say that e1 wraps e2, and that you can unwrap e1 to get e2.

Following this convention, we can give the QueryError type above an Unwrap method that returns its contained error:

func (e *QueryError) Unwrap() error { return e.Err }

The result of unwrapping an error may itself have an Unwrap method; we call the sequence of errors produced by repeated unwrapping the error chain.

Examining errors with Is and As

The Go 1.13 errors package includes two new functions for examining errors: Is and As.

The errors.Is function compares an error to a value.

// Similar to:
//   if err == ErrNotFound { … }
if errors.Is(err, ErrNotFound) {
    // something wasn't found
}

The As function tests whether an error is a specific type.

// Similar to:
//   if e, ok := err.(*QueryError); ok { … }
var e *QueryError
if errors.As(err, &e) {
    // err is a *QueryError, and e is set to the error's value
}

In the simplest case, the errors.Is function behaves like a comparison to a sentinel error, and the errors.As function behaves like a type assertion. When operating on wrapped errors, however, these functions consider all the errors in a chain. Let's look again at the example from above of unwrapping a QueryError to examine the underlying error:

if e, ok := err.(*QueryError); ok && e.Err == ErrPermission {
    // query failed because of a permission problem
}

Using the errors.Is function, we can write this as:

if errors.Is(err, ErrPermission) {
    // err, or some error that it wraps, is a permission problem
}

The errors package also includes a new Unwrap function which returns the result of calling an error's Unwrap method, or nil when the error has no Unwrap method. It is usually better to use errors.Is or errors.As, however, since these functions will examine the entire chain in a single call.

Wrapping errors with %w

As mentioned earlier, it is common to use the fmt.Errorf function to add additional information to an error.

if err != nil {
    return fmt.Errorf("decompress %v: %v", name, err)
}

In Go 1.13, the fmt.Errorf function supports a new %w verb. When this verb is present, the error returned by fmt.Errorf will have an Unwrap method returning the argument of %w, which must be an error. In all other ways, %w is identical to %v.

if err != nil {
    // Return an error which unwraps to err.
    return fmt.Errorf("decompress %v: %w", name, err)
}

Wrapping an error with %w makes it available to errors.Is and errors.As:

err := fmt.Errorf("access denied: %w", ErrPermission)
...
if errors.Is(err, ErrPermission) ...

Whether to Wrap

When adding additional context to an error, either with fmt.Errorf or by implementing a custom type, you need to decide whether the new error should wrap the original. There is no single answer to this question; it depends on the context in which the new error is created. Wrap an error to expose it to callers. Do not wrap an error when doing so would expose implementation details.

As one example, imagine a Parse function which reads a complex data structure from an io.Reader. If an error occurs, we wish to report the line and column number at which it occurred. If the error occurs while reading from the io.Reader, we will want to wrap that error to allow inspection of the underlying problem. Since the caller provided the io.Reader to the function, it makes sense to expose the error produced by it.

In contrast, a function which makes several calls to a database probably should not return an error which unwraps to the result of one of those calls. If the database used by the function is an implementation detail, then exposing these errors is a violation of abstraction. For example, if the LookupUser function of your package pkg uses Go's database/sql package, then it may encounter a sql.ErrNoRows error. If you return that error with fmt.Errorf("accessing DB: %v", err) then a caller cannot look inside to find the sql.ErrNoRows. But if the function instead returns fmt.Errorf("accessing DB: %w", err), then a caller could reasonably write

err := pkg.LookupUser(...)
if errors.Is(err, sql.ErrNoRows) …

At that point, the function must always return sql.ErrNoRows if you don't want to break your clients, even if you switch to a different database package. In other words, wrapping an error makes that error part of your API. If you don't want to commit to supporting that error as part of your API in the future, you shouldn't wrap the error.

It’s important to remember that whether you wrap or not, the error text will be the same. A person trying to understand the error will have the same information either way; the choice to wrap is about whether to give programs additional information so they can make more informed decisions, or to withhold that information to preserve an abstraction layer.

Customizing error tests with Is and As methods

The errors.Is function examines each error in a chain for a match with a target value. By default, an error matches the target if the two are equal. In addition, an error in the chain may declare that it matches a target by implementing an Is method.

As an example, consider this error inspired by the Upspin error package which compares an error against a template, considering only fields which are non-zero in the template:

type Error struct {
    Path string
    User string
}

func (e *Error) Is(target error) bool {
    t, ok := target.(*Error)
    if !ok {
        return false
    }
    return (e.Path == t.Path || t.Path == "") &&
           (e.User == t.User || t.User == "")
}

if errors.Is(err, &Error{User: "someuser"}) {
    // err's User field is "someuser".
}

The errors.As function similarly consults an As method when present.

Errors and package APIs

A package which returns errors (and most do) should describe what properties of those errors programmers may rely on. A well-designed package will also avoid returning errors with properties that should not be relied upon.

The simplest specification is to say that operations either succeed or fail, returning a nil or non-nil error value respectively. In many cases, no further information is needed.

If we wish a function to return an identifiable error condition, such as "item not found," we might return an error wrapping a sentinel.

var ErrNotFound = errors.New("not found")

// FetchItem returns the named item.
//
// If no item with the name exists, FetchItem returns an error
// wrapping ErrNotFound.
func FetchItem(name string) (*Item, error) {
    if itemNotFound(name) {
        return nil, fmt.Errorf("%q: %w", name, ErrNotFound)
    }
    // ...
}

There are other existing patterns for providing errors which can be semantically examined by the caller, such as directly returning a sentinel value, a specific type, or a value which can be examined with a predicate function.

In all cases, care should be taken not to expose internal details to the user. As we touched on in "Whether to Wrap" above, when you return an error from another package you should convert the error to a form that does not expose the underlying error, unless you are willing to commit to returning that specific error in the future.

f, err := os.Open(filename)
if err != nil {
    // The *os.PathError returned by os.Open is an internal detail.
    // To avoid exposing it to the caller, repackage it as a new
    // error with the same text. We use the %v formatting verb, since
    // %w would permit the caller to unwrap the original *os.PathError.
    return fmt.Errorf("%v", err)
}

If a function is defined as returning an error wrapping some sentinel or type, do not return the underlying error directly.

var ErrPermission = errors.New("permission denied")

// DoSomething returns an error wrapping ErrPermission if the user
// does not have permission to do something.
func DoSomething() error {
    if !userHasPermission() {
        // If we return ErrPermission directly, callers might come
        // to depend on the exact error value, writing code like this:
        //
        //     if err := pkg.DoSomething(); err == pkg.ErrPermission { … }
        //
        // This will cause problems if we want to add additional
        // context to the error in the future. To avoid this, we
        // return an error wrapping the sentinel so that users must
        // always unwrap it:
        //
        //     if err := pkg.DoSomething(); errors.Is(err, pkg.ErrPermission) { ... }
        return fmt.Errorf("%w", ErrPermission)
    }
    // ...
}

Conclusion

Although the changes we’ve discussed amount to just three functions and a formatting verb, we hope they will go a long way toward improving how errors are handled in Go programs. We expect that wrapping to provide additional context will become commonplace, helping programs to make better decisions and helping programmers to find bugs more quickly.

As Russ Cox said in his GopherCon 2019 keynote, on the path to Go 2 we experiment, simplify and ship. Now that we’ve shipped these changes, we look forward to the experiments that will follow.

Publishing Go Modules

Tyler Bui-Palsulich
26 September 2019

Introduction

This post is part 3 in a series.

This post discusses how to write and publish modules so other modules can depend on them.

Please note: this post covers development up to and including v1. If you are interested in v2, please see Go Modules: v2 and Beyond.

This post uses Git in examples. Mercurial, Bazaar, and others are supported as well.

Project setup

For this post, you'll need an existing project to use as an example. So, start with the files from the end of the Using Go Modules article:

$ cat go.mod
module example.com/hello

go 1.12

require rsc.io/quote/v3 v3.1.0

$ cat go.sum
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

$ cat hello.go
package hello

import "rsc.io/quote/v3"

func Hello() string {
    return quote.HelloV3()
}

func Proverb() string {
    return quote.Concurrency()
}

$ cat hello_test.go
package hello

import (
    "testing"
)

func TestHello(t *testing.T) {
    want := "Hello, world."
    if got := Hello(); got != want {
        t.Errorf("Hello() = %q, want %q", got, want)
    }
}

func TestProverb(t *testing.T) {
    want := "Concurrency is not parallelism."
    if got := Proverb(); got != want {
        t.Errorf("Proverb() = %q, want %q", got, want)
    }
}

$

Next, create a new git repository and add an initial commit. If you're publishing your own project, be sure to include a LICENSE file. Change to the directory containing the go.mod then create the repo:

$ git init
$ git add LICENSE go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: initial commit"
$

Semantic versions and modules

Every required module in a go.mod has a semantic version, the minimum version of that dependency to use to build the module.

A semantic version has the form vMAJOR.MINOR.PATCH.

  • Increment the MAJOR version when you make a backwards incompatible change to the public API of your module. This should only be done when absolutely necessary.
  • Increment the MINOR version when you make a backwards compatible change to the API, like changing dependencies or adding a new function, method, struct field, or type.
  • Increment the PATCH version after making minor changes that don't affect your module's public API or dependencies, like fixing a bug.

You can specify pre-release versions by appending a hyphen and dot separated identifiers (for example, v1.0.1-alpha or v2.2.2-beta.2). Normal releases are preferred by the go command over pre-release versions, so users must ask for pre-release versions explicitly (for example, go get example.com/hello@v1.0.1-alpha) if your module has any normal releases.

v0 major versions and pre-release versions do not guarantee backwards compatibility. They let you refine your API before making stability commitments to your users. However, v1 major versions and beyond require backwards compatibility within that major version.

The version referenced in a go.mod may be an explicit release tagged in the repository (for example, v1.5.2), or it may be a pseudo-version based on a specific commit (for example, v0.0.0-20170915032832-14c0d48ead0c). Pseudo-versions are a special type of pre-release version. Pseudo-versions are useful when a user needs to depend on a project that has not published any semantic version tags, or develop against a commit that hasn't been tagged yet, but users should not assume that pseudo-versions provide a stable or well-tested API. Tagging your modules with explicit versions signals to your users that specific versions are fully tested and ready to use.

Once you start tagging your repo with versions, it's important to keep tagging new releases as you develop your module. When users request a new version of your module (with go get -u or go get example.com/hello), the go command will choose the greatest semantic release version available, even if that version is several years old and many changes behind the primary branch. Continuing to tag new releases will make your ongoing improvements available to your users.

Do not delete version tags from your repo. If you find a bug or a security issue with a version, release a new version. If people depend on a version that you have deleted, their builds may fail. Similarly, once you release a version, do not change or overwrite it. The module mirror and checksum database store modules, their versions, and signed cryptographic hashes to ensure that the build of a given version remains reproducible over time.

v0: the initial, unstable version

Let's tag the module with a v0 semantic version. A v0 version does not make any stability guarantees, so nearly all projects should start with v0 as they refine their public API.

Tagging a new version has a few steps:

1. Run go mod tidy, which removes any dependencies the module might have accumulated that are no longer necessary.

2. Run go test ./... a final time to make sure everything is working.

3. Tag the project with a new version using git tag.

4. Push the new tag to the origin repository.

$ go mod tidy
$ go test ./...
ok      example.com/hello       0.015s
$ git add go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: changes for v0.1.0"
$ git tag v0.1.0
$ git push origin v0.1.0
$

Now other projects can depend on v0.1.0 of example.com/hello. For your own module, you can run go list -m example.com/hello@v0.1.0 to confirm the latest version is available (this example module does not exist, so no versions are available). If you don't see the latest version immediately and you're using the Go module proxy (the default since Go 1.13), try again in a few minutes to give the proxy time to load the new version.

If you add to the public API, make a breaking change to a v0 module, or upgrade the minor or version of one of your dependencies, increment the MINOR version for your next release. For example, the next release after v0.1.0 would be v0.2.0.

If you fix a bug in an existing version, increment the PATCH version. For example, the next release after v0.1.0 would be v0.1.1.

v1: the first stable version

Once you are absolutely sure your module's API is stable, you can release v1.0.0. A v1 major version communicates to users that no incompatible changes will be made to the module's API. They can upgrade to new v1 minor and patch releases, and their code should not break. Function and method signatures will not change, exported types will not be removed, and so on. If there are changes to the API, they will be backwards compatible (for example, adding a new field to a struct) and will be included in a new minor release. If there are bug fixes (for example, a security fix), they will be included in a patch release (or as part of a minor release).

Sometimes, maintaining backwards compatibility can lead to awkward APIs. That's OK. An imperfect API is better than breaking users' existing code.

The standard library's strings package is a prime example of maintaining backwards compatibility at the cost of API consistency.

  • Split slices a string into all substrings separated by a separator and returns a slice of the substrings between those separators.
  • SplitN can be used to control the number of substrings to return.

However, Replace took a count of how many instances of the string to replace from the beginning (unlike Split).

Given Split and SplitN, you would expect functions like Replace and ReplaceN. But, we couldn't change the existing Replace without breaking callers, which we promised not to do. So, in Go 1.12, we added a new function, ReplaceAll. The resulting API is a little odd, since Split and Replace behave differently, but that inconsistency is better than a breaking change.

Let's say you're happy with the API of example.com/hello and you want to release v1 as the first stable version.

Tagging v1 uses the same process as tagging a v0 version: run go mod tidy and go test ./..., tag the version, and push the tag to the origin repository:

$ go mod tidy
$ go test ./...
ok      example.com/hello       0.015s
$ git add go.mod go.sum hello.go hello_test.go
$ git commit -m "hello: changes for v1.0.0"
$ git tag v1.0.0
$ git push origin v1.0.0
$

At this point, the v1 API of example.com/hello is solidified. This communicates to everyone that our API is stable and they should feel comfortable using it.

Conclusion

This post walked through the process of tagging a module with semantic versions and when to release v1. A future post will cover how to maintain and publish modules at v2 and beyond.

To provide feedback and help shape the future of dependency management in Go, please send us bug reports or experience reports.

Thanks for all your feedback and help improving Go modules.

See the index for more articles.