| # Experiment, Simplify, Ship |
| 1 Aug 2019 |
| Tags: community, go2, proposals |
| Summary: How we develop Go, a talk from GopherCon 2019. |
| |
| Russ Cox |
| |
| ## Introduction |
| |
| This is the blog post version of my talk last week at GopherCon 2019. |
| |
| .iframe //www.youtube.com/embed/kNHo788oO5Y?rel=0 309 549 |
| |
| We are all on the path to Go 2, together, |
| but none of us know exactly where that path leads |
| or sometimes even which direction the path goes. |
| This post discusses how we actually |
| find and follow the path to Go 2. |
| Here’s what the process looks like. |
| |
| <div style="margin-left: 2em;"> |
| .image experiment/expsimp1.png _ 179 |
| </div> |
| |
| We experiment with Go as it exists now, |
| to understand it better, |
| learning what works well and what doesn’t. |
| Then we experiment with possible changes, |
| to understand them better, |
| again learning what works well and what doesn’t. |
| Based on what we learn from those experiments, |
| we simplify. |
| And then we experiment again. |
| And then we simplify again. |
| And so on. |
| And so on. |
| |
| ## The Four R’s of Simplifying |
| |
| During this process, there are four main ways that we can simplify |
| the overall experience of writing Go programs: |
| reshaping, redefining, removing, and restricting. |
| |
| **Simplify by Reshaping** |
| |
| The first way we simplify is by reshaping what exists into a new form, |
| one that ends up being simpler overall. |
| |
| Every Go program we write serves as an experiment to test Go itself. |
| In the early days of Go, we quickly learned that |
| it was common to write code like this `addToList` function: |
| |
| func addToList(list []int, x int) []int { |
| n := len(list) |
| if n+1 > cap(list) { |
| big := make([]int, n, (n+5)*2) |
| copy(big, list) |
| list = big |
| } |
| list = list[:n+1] |
| list[n] = x |
| return list |
| } |
| |
| We’d write the same code for slices of bytes, |
| and slices of strings, and so on. |
| Our programs were too complex, because Go was too simple. |
| |
| So we took the many functions like `addToList` in our programs |
| and reshaped them into one function provided by Go itself. |
| Adding `append` made the Go language a little more complex, |
| but on balance |
| it made the overall experience of writing Go programs simpler, |
| even after accounting for the cost of learning about `append`. |
| |
| Here’s another example. |
| For Go 1, we looked at the very many development tools |
| in the Go distribution, and we reshaped them into one new command. |
| |
| 5a 8g |
| 5g 8l |
| 5l cgo |
| 6a gobuild |
| 6cov gofix → go |
| 6g goinstall |
| 6l gomake |
| 6nm gopack |
| 8a govet |
| |
| The `go` command is so central now that |
| it is easy to forget that we went so long without it and how much extra work that involved. |
| |
| We added code and complexity to the Go distribution, |
| but on balance we simplified the experience of writing Go programs. |
| The new structure also created space for other interesting experiments, |
| which we’ll see later. |
| |
| **Simplify by Redefining** |
| |
| A second way we simplify is by redefining |
| functionality we already have, |
| allowing it to do more. |
| Like simplifying by reshaping, |
| simplifying by redefining makes programs simpler to write, |
| but now with nothing new to learn. |
| |
| For example, `append` was originally defined to read only from slices. |
| When appending to a byte slice, you could append the bytes from another byte slice, |
| but not the bytes from a string. |
| We redefined append to allow appending from a string, |
| without adding anything new to the language. |
| |
| var b []byte |
| var more []byte |
| b = append(b, more...) // ok |
| |
| var b []byte |
| var more string |
| b = append(b, more...) // ok later |
| |
| **Simplify by Removing** |
| |
| A third way we simplify is by removing functionality |
| when it has turned out to be less useful |
| or less important than we expected. |
| Removing functionality means one less thing to learn, |
| one less thing to fix bugs in, |
| one less thing to be distracted by or use incorrectly. |
| Of course, removing also |
| forces users to update existing programs, |
| perhaps making them more complex, |
| to make up for the removal. |
| But the overall result can still be that the |
| process of writing Go programs becomes simpler. |
| |
| An example of this is when we removed |
| the boolean forms of non-blocking channel operations from the language: |
| |
| ok := c <- x // before Go 1, was non-blocking send |
| x, ok := <-c // before Go 1, was non-blocking receive |
| |
| These operations were also possible to do using `select`, |
| making it confusing to need to decide which form to use. |
| Removing them simplified the language without reducing its power. |
| |
| **Simplify by Restricting** |
| |
| We can also simplify by restricting what is allowed. |
| From day one, Go has restricted the encoding of Go source files: |
| they must be UTF-8. |
| This restriction makes every program that tries to read Go source files simpler. |
| Those programs don’t have to worry about Go source files |
| encoded in Latin-1 or UTF-16 or UTF-7 or anything else. |
| |
| Another important restriction is `gofmt` for program formatting. |
| Nothing rejects Go code that isn’t formatted using `gofmt`, |
| but we have established a convention that tools that rewrite Go programs |
| leave them in `gofmt` form. |
| If you keep your programs in `gofmt` form too, |
| then these rewriters don’t make any formatting changes. |
| When you compare before and after, |
| the only diffs you see are real changes. |
| This restriction has simplified program rewriters |
| and led to successful experiments like |
| `goimports`, `gorename`, and many others. |
| |
| ## Go Development Process |
| |
| This cycle of experiment and simplify is a good model for what we’ve been doing the past ten years. |
| but it has a problem: |
| it’s too simple. |
| We can’t only experiment and simplify. |
| |
| We have to ship the result. |
| We have to make it available to use. |
| Of course, using it enables more experiments, |
| and possibly more simplifying, |
| and the process cycles on and on. |
| |
| <div style="margin-left: 2em;"> |
| .image experiment/expsimp2.png _ 326 |
| </div> |
| |
| We shipped Go to all of you for the first time |
| on November 10, 2009. |
| Then, with your help, we shipped Go 1 together in March 2012. |
| And we’ve shipped twelve Go releases since then. |
| All of these were important milestones, |
| to enable more experimentation, |
| to help us learn more about Go, |
| and of course to make Go available for production use. |
| |
| When we shipped Go 1, |
| we explicitly shifted our focus to using Go, |
| to understand this version of the language much better |
| before trying any more simplifications involving |
| language changes. |
| We needed to take time to experiment, |
| to really understand what works and what doesn’t. |
| |
| Of course, we’ve had twelve releases since Go 1, |
| so we have still been experimenting and simplifying and shipping. |
| But we’ve focused on ways to simplify Go development |
| without significant language changes and without breaking |
| existing Go programs. |
| For example, Go 1.5 shipped the first concurrent garbage collector |
| and then the following releases improved it, |
| simplifying Go development by removing pause times as an ongoing concern. |
| |
| At Gophercon in 2017, we announced that after five years of |
| experimentation, it was again time |
| to think about |
| significant changes that would simplify Go development. |
| Our path to Go 2 is really the same as the path to Go 1: |
| experiment and simplify and ship, |
| towards an overall goal of simplifying Go development. |
| |
| For Go 2, the concrete topics that we believed were |
| most important to address are |
| error handling, generics, and dependencies. |
| Since then we have realized that another |
| important topic is developer tooling. |
| |
| The rest of this post discusses how |
| our work in each of these areas |
| follows that path. |
| Along the way, |
| we’ll take one detour, |
| stopping to inspect the technical detail |
| of what will be shipping soon in Go 1.13 |
| for error handling. |
| |
| ## Errors |
| |
| It is hard enough to write a program |
| that works the right way in all cases |
| when all the inputs are valid and correct |
| and nothing the program depends on is failing. |
| When you add errors into the mix, |
| writing a program that works the right way |
| no matter what goes wrong is even harder. |
| |
| As part of thinking about Go 2, |
| we want to understand better |
| whether Go can help make that job any simpler. |
| |
| There are two different aspects that could |
| potentially be simplified: |
| error values and error syntax. |
| We’ll look at each in turn, |
| with the technical detour I promised focusing |
| on the Go 1.13 error value changes. |
| |
| **Error Values** |
| |
| Error values had to start somewhere. |
| Here is the `Read` function from the first version of the `os` package: |
| |
| export func Read(fd int64, b *[]byte) (ret int64, errno int64) { |
| r, e := syscall.read(fd, &b[0], int64(len(b))); |
| return r, e |
| } |
| |
| There was no `File` type yet, and also no error type. |
| `Read` and the other functions in the package |
| returned an `errno int64` directly from the underlying Unix system call. |
| |
| This code was checked in on September 10, 2008 at 12:14pm. |
| Like everything back then, it was an experiment, |
| and code changed quickly. |
| Two hours and five minutes later, the API changed: |
| |
| export type Error struct { s string } |
| |
| func (e *Error) Print() { … } // to standard error! |
| func (e *Error) String() string { … } |
| |
| export func Read(fd int64, b *[]byte) (ret int64, err *Error) { |
| r, e := syscall.read(fd, &b[0], int64(len(b))); |
| return r, ErrnoToError(e) |
| } |
| |
| This new API introduced the first `Error` type. |
| An error held a string and could return that string |
| and also print it to standard error. |
| |
| The intent here was to generalize beyond integer codes. |
| We knew from past experience |
| that operating system error numbers were too limited |
| a representation, |
| that it would simplify programs not to have to shoehorn |
| all detail about an error into 64 bits. |
| Using error strings had worked reasonably well |
| for us in the past, so we did the same here. |
| This new API lasted seven months. |
| |
| The next April, after more experience using interfaces, |
| we decided to generalize further |
| and allow user-defined error implementations, |
| by making the `os.Error` type itself an interface. |
| We simplified by removing the `Print` method. |
| |
| For Go 1 two years later, |
| based on a suggestion by Roger Peppe, |
| `os.Error` became the built-in `error` type, |
| and the `String` method was renamed to `Error`. |
| Nothing has changed since then. |
| But we have written many Go programs, |
| and as a result we have experimented a lot with how |
| best to implement and use errors. |
| |
| **Errors Are Values** |
| |
| Making `error` a simple interface |
| and allowing many different implementations |
| means we have the entire Go language |
| available to define and inspect errors. |
| We like to say that [errors are values](https://blog.golang.org/errors-are-values), |
| the same as any other Go value. |
| |
| Here’s an example. |
| On Unix, |
| an attempt to dial a network connection |
| ends up using the `connect` system call. |
| That system call returns a `syscall.Errno`, |
| which is a named integer type that represents |
| a system call error number |
| and implements the `error` interface: |
| |
| package syscall |
| |
| type Errno int64 |
| |
| func (e Errno) Error() string { ... } |
| |
| const ECONNREFUSED = Errno(61) |
| |
| ... err == ECONNREFUSED ... |
| |
| The `syscall` package also defines named constants |
| for the host operating system’s defined error numbers. |
| In this case, on this system, `ECONNREFUSED` is number 61. |
| Code that gets an error from a function |
| can test whether the error is `ECONNREFUSED` |
| using ordinary [value equality](https://golang.org/ref/spec#Comparison_operators). |
| |
| Moving up a level, |
| in package `os`, |
| any system call failure is reported using |
| a larger error structure that records what |
| operation was attempted in addition to the error. |
| There are a handful of these structures. |
| This one, `SyscallError`, describes an error |
| invoking a specific system call |
| with no additional information recorded: |
| |
| package os |
| |
| type SyscallError struct { |
| Syscall string |
| Err error |
| } |
| |
| func (e *SyscallError) Error() string { |
| return e.Syscall + ": " + e.Err.Error() |
| } |
| |
| Moving up another level, |
| in package `net`, |
| any network failure is reported using an even |
| larger error structure that records the details |
| of the surrounding network operation, |
| such as dial or listen, |
| and the network and addresses involved: |
| |
| package net |
| |
| type OpError struct { |
| Op string |
| Net string |
| Source Addr |
| Addr Addr |
| Err error |
| } |
| |
| func (e *OpError) Error() string { ... } |
| |
| Putting these together, |
| the errors returned by operations like `net.Dial` can format as strings, |
| but they are also structured Go data values. |
| In this case, the error is a `net.OpError`, which adds context |
| to an `os.SyscallError`, which adds context to a `syscall.Errno`: |
| |
| c, err := net.Dial("tcp", "localhost:50001") |
| |
| // "dial tcp [::1]:50001: connect: connection refused" |
| |
| err is &net.OpError{ |
| Op: "dial", |
| Net: "tcp", |
| Addr: &net.TCPAddr{IP: ParseIP("::1"), Port: 50001}, |
| Err: &os.SyscallError{ |
| Syscall: "connect", |
| Err: syscall.Errno(61), // == ECONNREFUSED |
| }, |
| } |
| |
| When we say errors are values, we mean both that |
| the entire Go language is available to define them |
| and also that |
| the entire Go language is available to inspect them. |
| |
| Here is an example from package net. |
| It turns out that when you attempt a socket connection, |
| most of the time you will get connected or get connection refused, |
| but sometimes you can get a spurious `EADDRNOTAVAIL`, |
| for no good reason. |
| Go shields user programs from this failure mode by retrying. |
| To do this, it has to inspect the error structure to find out |
| whether the `syscall.Errno` deep inside is `EADDRNOTAVAIL`. |
| |
| Here is the code: |
| |
| func spuriousENOTAVAIL(err error) bool { |
| if op, ok := err.(*OpError); ok { |
| err = op.Err |
| } |
| if sys, ok := err.(*os.SyscallError); ok { |
| err = sys.Err |
| } |
| return err == syscall.EADDRNOTAVAIL |
| } |
| |
| A [type assertion](https://golang.org/ref/spec#Type_assertions) peels away any `net.OpError` wrapping. |
| And then a second type assertion peels away any `os.SyscallError` wrapping. |
| And then the function checks the unwrapped error for equality with `EADDRNOTAVAIL`. |
| |
| What we’ve learned from years of experience, |
| from this experimenting with Go errors, |
| is that it is very powerful to be able to define |
| arbitrary implementations of the `error` interface, |
| to have the full Go language available |
| both to construct and to deconstruct errors, |
| and not to require the use of any single implementation. |
| |
| These properties—that errors are values, |
| and that there is not one required error implementation—are |
| important to preserve. |
| |
| Not mandating one error implementation |
| enabled everyone to experiment with |
| additional functionality that an error might provide, |
| leading to many packages, |
| such as |
| [github.com/pkg/errors](https://godoc.org/github.com/pkg/errors), |
| [gopkg.in/errgo.v2](https://godoc.org/gopkg.in/errgo.v2), |
| [github.com/hashicorp/errwrap](https://godoc.org/github.com/hashicorp/errwrap), |
| [upspin.io/errors](https://godoc.org/upspin.io/errors), |
| [github.com/spacemonkeygo/errors](https://godoc.org/github.com/spacemonkeygo/errors), |
| and more. |
| |
| One problem with unconstrained experimentation, |
| though, is that as a client |
| you have to program to the union of |
| all the possible implementations you might encounter. |
| A simplification that seemed worth exploring for Go 2 |
| was to define a standard version of commonly-added functionality, |
| in the form of agreed-upon optional interfaces, |
| so that different implementations could interoperate. |
| |
| **Unwrap** |
| |
| The most commonly-added functionality |
| in these packages is some method that can be |
| called to remove context from an error, |
| returning the error inside. |
| Packages use different names and meanings |
| for this operation, and sometimes it removes one level of context, |
| while sometimes it removes as many levels as possible. |
| |
| For Go 1.13, we have introduced a convention that an error |
| implementation adding removable context to an inner error |
| should implement an `Unwrap` method that returns the inner error, |
| unwrapping the context. |
| If there is no inner error appropriate to expose to callers, |
| either the error shouldn’t have an `Unwrap` method, |
| or the `Unwrap` method should return nil. |
| |
| // Go 1.13 optional method for error implementations. |
| |
| interface { |
| // Unwrap removes one layer of context, |
| // returning the inner error if any, or else nil. |
| Unwrap() error |
| } |
| |
| The way to call this optional method is to invoke the helper function `errors.Unwrap`, |
| which handles cases like the error itself being nil or not having an `Unwrap` method at all. |
| |
| package errors |
| |
| // Unwrap returns the result of calling |
| // the Unwrap method on err, |
| // if err’s type defines an Unwrap method. |
| // Otherwise, Unwrap returns nil. |
| func Unwrap(err error) error |
| |
| We can use the `Unwrap` method |
| to write a simpler, more general version of `spuriousENOTAVAIL`. |
| Instead of looking for specific error wrapper implementations |
| like `net.OpError` or `os.SyscallError`, |
| the general version can loop, calling `Unwrap` to remove context, |
| until either it reaches `EADDRNOTAVAIL` or there’s no error left: |
| |
| func spuriousENOTAVAIL(err error) bool { |
| for err != nil { |
| if err == syscall.EADDRNOTAVAIL { |
| return true |
| } |
| err = errors.Unwrap(err) |
| } |
| return false |
| } |
| |
| This loop is so common, though, that Go 1.13 defines a second function, `errors.Is`, |
| that repeatedly unwraps an error looking for a specific target. |
| So we can replace the entire loop with a single call to `errors.Is`: |
| |
| func spuriousENOTAVAIL(err error) bool { |
| return errors.Is(err, syscall.EADDRNOTAVAIL) |
| } |
| |
| At this point we probably wouldn’t even define the function; |
| it would be equally clear, and simpler, to call `errors.Is` directly at the call sites. |
| |
| Go 1.13 also introduces a function `errors.As` |
| that unwraps until it finds a specific implementation type. |
| |
| If you want to write code that works with |
| arbitrarily-wrapped errors, |
| `errors.Is` is the wrapper-aware |
| version of an error equality check: |
| |
| err == target |
| |
| → |
| |
| errors.Is(err, target) |
| |
| And `errors.As` is the wrapper-aware |
| version of an error type assertion: |
| |
| target, ok := err.(*Type) |
| if ok { |
| ... |
| } |
| |
| → |
| |
| var target *Type |
| if errors.As(err, &target) { |
| ... |
| } |
| |
| **To Unwrap Or Not To Unwrap?** |
| |
| Whether to make it possible to unwrap an error is an API decision, |
| the same way that whether to export a struct field is an API decision. |
| Sometimes it is appropriate to expose that detail to calling code, |
| and sometimes it isn’t. |
| When it is, implement Unwrap. |
| When it isn’t, don’t implement Unwrap. |
| |
| Until now, `fmt.Errorf` has not exposed |
| an underlying error formatted with `%v` to caller inspection. |
| That is, the result of `fmt.Errorf` has not been possible to unwrap. |
| Consider this example: |
| |
| // errors.Unwrap(err2) == nil |
| // err1 is not available (same as earlier Go versions) |
| err2 := fmt.Errorf("connect: %v", err1) |
| |
| If `err2` is returned to |
| a caller, that caller has never had any way to open up `err2` and access `err1`. |
| We preserved that property in Go 1.13. |
| |
| For the times when you do want to allow unwrapping the result of `fmt.Errorf`, |
| we also added a new printing verb `%w`, which formats like `%v`, |
| requires an error value argument, |
| and makes the resulting error’s `Unwrap` method return that argument. |
| In our example, suppose we replace `%v` with `%w`: |
| |
| // errors.Unwrap(err4) == err3 |
| // (%w is new in Go 1.13) |
| err4 := fmt.Errorf("connect: %w", err3) |
| |
| Now, if `err4` is returned to a caller, |
| the caller can use `Unwrap` to retrieve `err3`. |
| |
| It is important to note that absolute rules like |
| “always use `%v` (or never implement `Unwrap`)” or “always use `%w` (or always implement `Unwrap`)” |
| are as wrong as absolute rules like “never export struct fields” or “always export struct fields.” |
| Instead, the right decision depends on |
| whether callers should be able to inspect and depend on |
| the additional information that using `%w` or implementing `Unwrap` exposes. |
| |
| As an illustration of this point, |
| every error-wrapping type in the standard library |
| that already had an exported `Err` field |
| now also has an `Unwrap` method returning that field, |
| but implementations with unexported error fields do not, |
| and existing uses of `fmt.Errorf` with `%v` still use `%v`, not `%w`. |
| |
| **Error Value Printing (Abandoned)** |
| |
| Along with the design draft for Unwrap, |
| we also published a |
| [design draft for an optional method for richer error printing](https://golang.org/design/go2draft-error-printing), |
| including stack frame information |
| and support for localized, translated errors. |
| |
| // Optional method for error implementations |
| type Formatter interface { |
| Format(p Printer) (next error) |
| } |
| |
| // Interface passed to Format |
| type Printer interface { |
| Print(args ...interface{}) |
| Printf(format string, args ...interface{}) |
| Detail() bool |
| } |
| |
| This one is not as simple as `Unwrap`, |
| and I won’t go into the details here. |
| As we discussed the design with the Go community over the winter, |
| we learned that the design wasn’t simple enough. |
| It was too hard for individual error types to implement, |
| and it did not help existing programs enough. |
| On balance, it did not simplify Go development. |
| |
| As a result of this community discussion, |
| we abandoned this printing design. |
| |
| **Error Syntax** |
| |
| That was error values. |
| Let’s look briefly at error syntax, |
| another abandoned experiment. |
| |
| Here is some code from |
| [`compress/lzw/writer.go`](https://go.googlesource.com/go/+/go1.12/src/compress/lzw/writer.go#209) in the standard library: |
| |
| // Write the savedCode if valid. |
| if e.savedCode != invalidCode { |
| if err := e.write(e, e.savedCode); err != nil { |
| return err |
| } |
| if err := e.incHi(); err != nil && err != errOutOfCodes { |
| return err |
| } |
| } |
| |
| // Write the eof code. |
| eof := uint32(1)<<e.litWidth + 1 |
| if err := e.write(e, eof); err != nil { |
| return err |
| } |
| |
| At a glance, this code is about half error checks. |
| My eyes glaze over when I read it. |
| And we know that code that is tedious to write and tedious to read is easy to misread, |
| making it a good home for hard-to-find bugs. |
| For example, one of these three error checks is not like the others, |
| a fact that is easy to miss on a quick skim. |
| If you were debugging this code, how long would it take to notice that? |
| |
| At Gophercon last year we |
| [presented a draft design](https://golang.org/design/go2draft-error-handling) |
| for a new control flow construct marked by the keyword `check`. |
| `Check` consumes the error result from a function call or expression. |
| If the error is non-nil, the `check` returns that error. |
| Otherwise the `check` evaluates to the other results |
| from the call. We can use `check` to simplify the lzw code: |
| |
| // Write the savedCode if valid. |
| if e.savedCode != invalidCode { |
| check e.write(e, e.savedCode) |
| if err := e.incHi(); err != errOutOfCodes { |
| check err |
| } |
| } |
| |
| // Write the eof code. |
| eof := uint32(1)<<e.litWidth + 1 |
| check e.write(e, eof) |
| |
| This version of the same code uses `check`, |
| which removes four lines of code and |
| more importantly highlights that |
| the call to `e.incHi` is allowed to return `errOutOfCodes`. |
| |
| Maybe most importantly, |
| the design also allowed defining error handler blocks |
| to be run when later checks failed. |
| That would let you write shared context-adding code just once, |
| like in this snippet: |
| |
| handle err { |
| err = fmt.Errorf("closing writer: %w", err) |
| } |
| |
| // Write the savedCode if valid. |
| if e.savedCode != invalidCode { |
| check e.write(e, e.savedCode) |
| if err := e.incHi(); err != errOutOfCodes { |
| check err |
| } |
| } |
| |
| // Write the eof code. |
| eof := uint32(1)<<e.litWidth + 1 |
| check e.write(e, eof) |
| |
| In essence, `check` was a short way to write the `if` statement, |
| and `handle` was like |
| [`defer`](https://golang.org/ref/spec#Defer_statements) but only for error return paths. |
| In contrast to exceptions in other languages, |
| this design retained Go’s important property that |
| every potential failing call was marked explicitly in the code, |
| now using the `check` keyword instead of `if err != nil`. |
| |
| The big problem with this design |
| was that `handle` overlapped too much, |
| and in confusing ways, with `defer`. |
| |
| In May we posted |
| [a new design with three simplifications](https://golang.org/design/32437-try-builtin): |
| to avoid the confusion with `defer`, the design dropped `handle` in favor of just using `defer`; |
| to match a similar idea in Rust and Swift, the design renamed `check` to `try`; |
| and to allow experimentation in a way that existing parsers like `gofmt` would recognize, |
| it changed `check` (now `try`) from a keyword to a built-in function. |
| |
| Now the same code would look like this: |
| |
| defer errd.Wrapf(&err, "closing writer") |
| |
| // Write the savedCode if valid. |
| if e.savedCode != invalidCode { |
| try(e.write(e, e.savedCode)) |
| if err := e.incHi(); err != errOutOfCodes { |
| try(err) |
| } |
| } |
| |
| // Write the eof code. |
| eof := uint32(1)<<e.litWidth + 1 |
| try(e.write(e, eof)) |
| |
| We spent most of June discussing this proposal publicly on GitHub. |
| |
| The fundamental idea of `check` or `try` was to shorten |
| the amount of syntax repeated at each error check, |
| and in particular to remove the `return` statement from view, |
| keeping the error check explicit and better highlighting interesting variations. |
| One interesting point raised during the public feedback discussion, |
| however, was that without an explicit `if` statement and `return`, |
| there’s nowhere to put a debugging print, |
| there’s nowhere to put a breakpoint, |
| and there’s no code to show as unexecuted in code coverage results. |
| The benefits we were after |
| came at the cost of making these situations more complex. |
| On balance, from this as well as other considerations, |
| it was not at all clear that the overall result would |
| be simpler Go development, |
| so we abandoned this experiment. |
| |
| That’s everything about error handling, |
| which was one of the main focuses for this year. |
| |
| ## Generics |
| |
| Now for something a little less controversial: generics. |
| |
| The second big topic we identified for Go 2 was |
| some kind of way to write code with |
| type parameters. |
| This would enable writing generic data structures |
| and also writing generic functions that |
| work with any kind of slice, |
| or any kind of channel, |
| or any kind of map. |
| For example, here is a generic channel filter: |
| |
| // Filter copies values from c to the returned channel, |
| // passing along only those values satisfying f. |
| func Filter(type value)(f func(value) bool, c <-chan value) <-chan value { |
| out := make(chan value) |
| go func() { |
| for v := range c { |
| if f(v) { |
| out <- v |
| } |
| } |
| close(out) |
| }() |
| return out |
| } |
| |
| We’ve been thinking about generics since work on Go began, |
| and we wrote and rejected our first concrete design in 2010. |
| We wrote and rejected three more designs by the end of 2013. |
| Four abandoned experiments, |
| but not failed experiments, |
| We learned from them, |
| like we learned from `check` and `try`. |
| Each time, we learned that the path to Go 2 is not in that exact direction, |
| and we noticed other directions that might be interesting to explore. |
| But by 2013 we had decided that we needed to focus on other concerns, |
| so we put the entire topic aside for a few years. |
| |
| Last year we started exploring and experimenting again, |
| and we presented a |
| [new design](https://github.com/golang/proposal/blob/master/design/go2draft-contracts.md), |
| based on the idea of a contract, |
| at Gophercon last summer. |
| We’ve continued to experiment and simplify, |
| and we’ve been working |
| with programming language theory experts |
| to understand the design better. |
| |
| Overall, I am hopeful that we’re headed in a good direction, |
| toward a design that will simplify Go development. |
| Even so, we might find that this design doesn’t work either. |
| We might have to abandon this experiment |
| and adjust our path based on what we learned. |
| We’ll find out. |
| |
| At Gophercon 2019, Ian Lance Taylor talked about |
| why we might want to add generics to Go |
| and briefly previewed the latest design draft. |
| For details, see his blog post “[Why Generics?](https://blog.golang.org/why-generics)” |
| |
| ## Dependencies |
| |
| The third big topic we identified for Go 2 was dependency management. |
| |
| In 2010 we published a tool called `goinstall`, |
| which we called |
| “[an experiment in package installation](https://groups.google.com/forum/#!msg/golang-nuts/8JFwR3ESjjI/cy7qZzN7Lw4J).” |
| It downloaded dependencies and stored them in your |
| Go distribution tree, in GOROOT. |
| |
| As we experimented with `goinstall`, |
| we learned that the Go distribution and the installed packages |
| should be kept separate, |
| so that it was possible to change to a new Go distribution |
| without losing all your Go packages. |
| So in 2011 we introduced `GOPATH`, |
| an environment variable that specified |
| where to look for packages not found in the main Go distribution. |
| |
| Adding GOPATH created more places for Go packages |
| but simplified Go development overall, |
| by separating your Go distribution from your Go libraries. |
| |
| **Compatibility** |
| |
| The `goinstall` experiment intentionally left out |
| an explicit concept of package versioning. |
| Instead, `goinstall` always downloaded the latest copy. |
| We did this so we could focus on the other |
| design problems for package installation. |
| |
| `Goinstall` became `go get` as part of Go 1. |
| When people asked about versions, |
| we encouraged them to experiment by |
| creating additional tools, and they did. |
| And we encouraged package AUTHORS |
| to provide their USERS |
| with the same backwards compatibility |
| we did for the Go 1 libraries. |
| Quoting [the Go FAQ](https://golang.org/doc/faq#get_version): |
| |
| <div style="margin-left: 2em; font-style: italic;"> |
| |
| “Packages intended for public use should try to maintain backwards compatibility as they evolve. |
| |
| If different functionality is required, |
| add a new name instead of changing an old one. |
| |
| If a complete break is required, |
| create a new package with a new import path.” |
| |
| </div> |
| |
| This convention |
| simplifies the overall experience of using a package |
| by restricting what authors can do: |
| avoid breaking changes to APIs; |
| give new functionality a new name; |
| and |
| give a whole new package design a new import path. |
| |
| Of course, people kept experimenting. |
| One of the most interesting experiments |
| was started by Gustavo Niemeyer. |
| He created a Git redirector called |
| [`gopkg.in`](https://gopkg.in), |
| which provided different import paths |
| for different API versions, |
| to help package authors |
| follow the convention |
| of giving a new package design |
| a new import path. |
| |
| For example, |
| the Go source code in the GitHub repository |
| [go-yaml/yaml](https://github.com/go-yaml/yaml) |
| has different APIs |
| in the v1 and v2 semantic version tags. |
| The `gopkg.in` server provides these with |
| different import paths |
| [gopkg.in/yaml.v1](https://godoc.org/gopkg.in/yaml.v1) |
| and |
| [gopkg.in/yaml.v2](https://godoc.org/gopkg.in/yaml.v2). |
| |
| The convention of providing backwards compatibility, |
| so that a newer version of a package can be used |
| in place of an older version, |
| is what makes `go get`’s very simple rule—“always download the latest copy”—work well even today. |
| |
| **Versioning And Vendoring** |
| |
| But in production contexts you need to be more precise |
| about dependency versions, to make builds reproducible. |
| |
| Many people experimented with what that should look like, |
| building tools that served their needs, |
| including Keith Rarick’s `goven` (2012) and `godep` (2013), |
| Matt Butcher’s `glide` (2014), and Dave Cheney’s `gb` (2015). |
| All of these tools use the model that you copy dependency |
| packages into your own source control repository. |
| The exact mechanisms used |
| to make those packages available for import varied, |
| but they were all more complex than it seemed they should be. |
| |
| After a community-wide discussion, |
| we adopted a proposal by Keith Rarick |
| to add explicit support for referring to copied dependencies |
| without GOPATH tricks. |
| This was simplifying by reshaping: |
| like with `addToList` and `append`, |
| these tools were already implementing the concept, |
| but it was more awkward than it needed to be. |
| Adding explicit support for vendor directories |
| made these uses simpler overall. |
| |
| Shipping vendor directories in the `go` command |
| led to more experimentation with vendoring itself, |
| and we realized that we had introduced a few problems. |
| The most serious was that we lost _package uniqueness_. |
| Before, during any given build, |
| an import path |
| might appear in lots of different packages, |
| and all the imports referred to the same target. |
| Now with vendoring, the same import path in different |
| packages might refer to different vendored copies of the package, |
| all of which would appear in the final resulting binary. |
| |
| At the time, we didn’t have a name for this property: |
| package uniqueness. |
| It was just how the GOPATH model worked. |
| We didn’t completely appreciate it until it went away. |
| |
| There is a parallel here with the `check` and `try` |
| error syntax proposals. |
| In that case, we were relying |
| on how the visible `return` statement worked |
| in ways we didn’t appreciate |
| until we considered removing it. |
| |
| When we added vendor directory support, |
| there were many different tools for managing dependencies. |
| We thought that a clear agreement |
| about the format of vendor directories |
| and vendoring metadata |
| would allow the various tools to interoperate, |
| the same way that agreement about |
| how Go programs are stored in text files |
| enables interoperation |
| between the Go compiler, text editors, |
| and tools like `goimports` and `gorename`. |
| |
| This turned out to be naively optimistic. |
| The vendoring tools all differed in subtle semantic ways. |
| Interoperation would require changing them all |
| to agree about the semantics, |
| likely breaking their respective users. |
| Convergence did not happen. |
| |
| **Dep** |
| |
| At Gophercon in 2016, we started an effort |
| to define a single tool to manage dependencies. |
| As part of that effort, we conducted surveys |
| with many different kinds of users |
| to understand what they needed |
| as far as dependency management, |
| and a team started work on a new tool, |
| which became `dep`. |
| |
| `Dep` aimed to be able to replace all the |
| existing dependency management tools. |
| The goal was to simplify by reshaping the |
| existing different tools into a single one. |
| It partly accomplished that. |
| `Dep` also restored package uniqueness for its users, |
| by having only one vendor directory |
| at the top of the project tree. |
| |
| But `dep` also introduced a serious problem |
| that took us a while to fully appreciate. |
| The problem was that `dep` embraced a design choice from `glide`, |
| to support and encourage incompatible changes to a given package |
| without changing the import path. |
| |
| Here is an example. |
| Suppose you are building your own program, |
| and you need to have a configuration file, |
| so you use version 2 of a popular Go YAML package: |
| |
| <div style="margin-left: 2em;"> |
| .image experiment/yamldeps1.png _ 214 |
| </div> |
| |
| Now suppose your program |
| imports the Kubernetes client. |
| It turns out that Kubernetes uses YAML extensively, |
| and it uses version 1 of the same popular package: |
| |
| <div style="margin-left: 2em;"> |
| .image experiment/yamldeps2.png _ 557 |
| </div> |
| |
| Version 1 and version 2 have incompatible APIs, |
| but they also have different import paths, |
| so there is no ambiguity about which is meant by a given import. |
| Kubernetes gets version 1, |
| your config parser gets version 2, |
| and everything works. |
| |
| `Dep` abandoned this model. |
| Version 1 and version 2 of the yaml package would now |
| have the same import path, |
| producing a conflict. |
| Using the same import path for two incompatible versions, |
| combined with package uniqueness, |
| makes it impossible to build this program |
| that you could build before: |
| |
| <div style="margin-left: 2em;"> |
| .image experiment/yamldeps3.png _ 450 |
| </div> |
| |
| It took us a while to understand this problem, |
| because we had been applying the |
| “new API means new import path” |
| convention for so long that we took it for granted. |
| The dep experiment helped us |
| appreciate that convention better, |
| and we gave it a name: |
| the _import compatibility rule_: |
| |
| <div style="margin-left: 2em; font-style: italic;"> |
| |
| “If an old package and a new package have the same import path, |
| the new package must be backwards compatible with the old package.” |
| |
| </div> |
| |
| **Go Modules** |
| |
| We took what worked well in the dep experiment |
| and what we learned about what didn’t work well, |
| and we experimented with a new design, called `vgo`. |
| In `vgo`, packages followed the import compatibility rule, |
| so that we can provide package uniqueness |
| but still not break builds like the one we just looked at. |
| This let us simplify other parts of the design as well. |
| |
| Besides restoring the import compatibility rule, |
| another important part of the `vgo` design |
| was to give the concept of a group of packages a name |
| and to allow that grouping to be separated |
| from source code repository boundaries. |
| The name of a group of Go packages is a module, |
| so we refer to the system now as Go modules. |
| |
| Go modules are now integrated with the `go` command, |
| which avoids needing to copy around vendor directories at all. |
| |
| **Replacing GOPATH** |
| |
| With Go modules comes the end of GOPATH as a |
| global name space. |
| Nearly all the hard work of converting existing Go usage |
| and tools to modules is caused by this change, |
| from moving away from GOPATH. |
| |
| The fundamental idea of GOPATH |
| is that the GOPATH directory tree |
| is the global source of truth |
| for what versions are being used, |
| and the versions being used don’t change |
| as you move around between directories. |
| But the global GOPATH mode is in direct |
| conflict with the production requirement of |
| per-project reproducible builds, |
| which itself simplifies the Go development |
| and deployment experience in many important ways. |
| |
| Per-project reproducible builds means that |
| when you are working in a checkout of project A, |
| you get the same set of dependency versions that the other developers of project A get |
| at that commit, |
| as defined by the `go.mod` file. |
| When you switch to working in a checkout of project B, |
| now you get that project’s chosen dependency versions, |
| the same set that the other developers of project B get. |
| But those are likely different from project A. |
| The set of dependency versions |
| changing when you move from project A to project B |
| is necessary to keep your development in sync |
| with that of the other developers on A and on B. |
| There can’t be a single global GOPATH anymore. |
| |
| Most of the complexity of adopting modules |
| arises directly from the loss of the one global GOPATH. |
| Where is the source code for a package? |
| Before, the answer depended only on your GOPATH environment variable, |
| which most people rarely changed. |
| Now, the answer depends on what project you are working on, |
| which may change often. |
| Everything needs updating for this new convention. |
| |
| Most development tools use the |
| [`go/build`](https://godoc.org/go/build) package to find and load Go source code. |
| We’ve kept that package working, |
| but the API did not anticipate modules, |
| and the workarounds we added to avoid API changes |
| are slower than we’d like. |
| We’ve published a replacement, |
| [`golang.org/x/tools/go/packages`](https://godoc.org/golang.org/x/tools/go/packages). |
| Developer tools should now use that instead. |
| It supports both GOPATH and Go modules, |
| and it is faster and easier to use. |
| In a release or two we may move it into the standard library, |
| but for now [`golang.org/x/tools/go/packages`](https://godoc.org/golang.org/x/tools/go/packages) |
| is stable and ready for use. |
| |
| **Go Module Proxies** |
| |
| One of the ways modules simplify Go development |
| is by separating the concept of a group of packages |
| from the underlying source control repository |
| where they are stored. |
| |
| When we talked to Go users about dependencies, |
| almost everyone using Go at their companies |
| asked how to route `go get` package fetches |
| through their own servers, |
| to better control what code can be used. |
| And even open-source developers were concerned |
| about dependencies disappearing |
| or changing unexpectedly, |
| breaking their builds. |
| Before modules, users had attempted |
| complex solutions to these problems, |
| including intercepting the version control |
| commands that the `go` command runs. |
| |
| The Go modules design makes it easy |
| to introduce the idea of a module proxy |
| that can be asked for a specific module version. |
| |
| Companies can now easily run their own module proxy, |
| with custom rules about what is allowed |
| and where cached copies are stored. |
| The open-source [Athens project](https://docs.gomods.io) has built just such a proxy, |
| and Aaron Schlesinger gave a talk about it at Gophercon 2019. |
| (We’ll add a link here when the video becomes available.) |
| |
| And for individual developers and open source teams, |
| the Go team at Google has [launched a proxy](https://groups.google.com/forum/#!topic/golang-announce/0wo8cOhGuAI) that serves |
| as a public mirror of all open-source Go packages, |
| and Go 1.13 will use that proxy by default when in module mode. |
| Katie Hockman gave a talk about this system at Gophercon 2019. |
| (We’ll add a link here when the video becomes available.) |
| |
| **Go Modules Status** |
| |
| Go 1.11 introduced modules as an experimental, opt-in preview. |
| We keep experimenting and simplifying. |
| Go 1.12 shipped improvements, |
| and Go 1.13 will ship more improvements. |
| |
| Modules are now at the point |
| where we believe that they will serve most users, |
| but we aren’t ready to shut down GOPATH just yet. |
| We will keep experimenting, simplifying, and revising. |
| |
| We fully recgonize that |
| the Go user community |
| built up almost a decade of experience |
| and tooling and workflows around GOPATH, |
| and it will take a while to convert all of that to Go modules. |
| |
| But again, |
| we think that modules will now |
| work very well for most users, |
| and I encourage you to take a look |
| when Go 1.13 is released. |
| |
| As one data point, |
| the Kubernetes project has a lot of dependencies, |
| and they have migrated to using Go modules |
| to manage them. |
| You probably can too. |
| And if you can’t, |
| please let us know what’s not working for you |
| or what’s too complex, |
| by [filing a bug report](https://golang.org/issue/new), |
| and we will experiment and simplify. |
| |
| ## Tools |
| |
| Error handling, generics, and dependency management |
| are going to take a few more years at least, |
| and we’re going to focus on them for now. |
| Error handling is close to done, |
| modules will be next after that, |
| and maybe generics after that. |
| |
| But suppose we look a couple years out, |
| to when we are done experimenting and simplifying |
| and have shipped error handling, modules, and generics. |
| Then what? |
| It’s very difficult to predict the future, |
| but I think that once these three have shipped, |
| that may mark the start of a new quiet period for major changes. |
| Our focus at that point will likely shift to |
| simplifying Go development with improved tools. |
| |
| Some of the tool work is already underway, |
| so this post finishes by looking at that. |
| |
| While we helped update all the Go community’s |
| existing tools to understand Go modules, |
| we noticed that having a ton of development helper tools |
| that each do one small job is not serving users well. |
| The individual tools are too hard to combine, |
| too slow to invoke, and too different to use. |
| |
| We began an effort to unify the most commonly-required |
| development helpers into a single tool, |
| now called `gopls` (pronounced “go, please”). |
| `Gopls` speaks the |
| [Language Server Protocol, LSP](https://langserver.org/), |
| and works with any integrated development environment |
| or text editor with LSP support, |
| which is essentially everything at this point. |
| |
| `Gopls` marks an expansion in focus for the Go project, |
| from delivering standalone compiler-like, command-line |
| tools like go vet or gorename |
| to also delivering a complete IDE service. |
| Rebecca Stambler gave a talk with more details about `gopls` and IDEs at Gophercon 2019. |
| (We’ll add a link here when the video becomes available.) |
| |
| After `gopls`, we also have ideas for reviving `go fix` in an |
| extensible way and for making `go vet` even more helpful. |
| |
| ## Coda |
| |
| <div style="margin-left: 2em;"> |
| .image experiment/expsimp2.png _ 326 |
| </div> |
| |
| So there’s the path to Go 2. |
| We will experiment and simplify. |
| And experiment and simplify. |
| And ship. |
| And experiment and simplify. |
| And do it all again. |
| It may look or even feel like the path goes around in circles. |
| But each time we experiment and simplify |
| we learn a little more about what Go 2 should look like |
| and move another step closer to it. |
| Even abandoned experiments like `try` |
| or our first four generics designs |
| or `dep` are not wasted time. |
| They help us learn what needs to be |
| simplified before we can ship, |
| and in some cases they help us better understand |
| something we took for granted. |
| |
| At some point we will realize we have |
| experimented enough, and simplified enough, |
| and shipped enough, |
| and we will have Go 2. |
| |
| Thanks to all of you in the Go community |
| for helping us experiment |
| and simplify |
| and ship |
| and find our way on this path. |