diff options
author | Russ Cox <rsc@golang.org> | 2019-07-31 16:30:07 -0400 |
---|---|---|
committer | Russ Cox <rsc@golang.org> | 2019-08-01 15:26:22 +0000 |
commit | 7c94a9b929dc60a490cceb3f15190e80da707674 (patch) | |
tree | c2e590f4aa4f0193126cc48e88948eff52d27bd6 /content | |
parent | 5754551255d2cc2098697440d192b1e3555c3075 (diff) |
content: add experiment.article
This is the blog post form of my Gophercon 2019 talk.
Change-Id: I841aa8351711e60218df6dd8cec591ce83e3dd18
Reviewed-on: https://go-review.googlesource.com/c/blog/+/188381
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Andrew Bonventre <andybons@golang.org>
Reviewed-by: Emmanuel Odeke <emm.odeke@gmail.com>
Diffstat (limited to 'content')
-rw-r--r-- | content/experiment.article | 1332 | ||||
-rw-r--r-- | content/experiment/div-end.html | 1 | ||||
-rw-r--r-- | content/experiment/div-indent.html | 1 | ||||
-rw-r--r-- | content/experiment/div-quote.html | 1 | ||||
-rw-r--r-- | content/experiment/expsimp1.graffle | bin | 0 -> 6982 bytes | |||
-rw-r--r-- | content/experiment/expsimp1.png | bin | 0 -> 42820 bytes | |||
-rw-r--r-- | content/experiment/expsimp2.graffle | bin | 0 -> 7627 bytes | |||
-rw-r--r-- | content/experiment/expsimp2.png | bin | 0 -> 58180 bytes | |||
-rw-r--r-- | content/experiment/yamldeps1.graffle | bin | 0 -> 7289 bytes | |||
-rw-r--r-- | content/experiment/yamldeps1.png | bin | 0 -> 44262 bytes | |||
-rw-r--r-- | content/experiment/yamldeps2.graffle | bin | 0 -> 7807 bytes | |||
-rw-r--r-- | content/experiment/yamldeps2.png | bin | 0 -> 125856 bytes | |||
-rw-r--r-- | content/experiment/yamldeps3.graffle | bin | 0 -> 7896 bytes | |||
-rw-r--r-- | content/experiment/yamldeps3.png | bin | 0 -> 132644 bytes |
14 files changed, 1335 insertions, 0 deletions
diff --git a/content/experiment.article b/content/experiment.article new file mode 100644 index 0000000..1494b2e --- /dev/null +++ b/content/experiment.article @@ -0,0 +1,1332 @@ +Experiment, Simplify, Ship +01 Aug 2019 +Tags: community, go2, proposals + +Russ Cox + +* Introduction + +[This is the blog post version of my talk last week at Gophercon 2019. +We will add a video link to the talk once it is available.] + +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. + +.html experiment/div-indent.html +.image experiment/expsimp1.png _ 179 +.html experiment/div-end.html + +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. + +.html experiment/div-indent.html +.image experiment/expsimp2.png _ 326 +.html experiment/div-end.html + +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 2011. +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. + +When we shipped Go 1, +we shifted to focus explicitly on 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 [[https://blog.golang.org/errors-are-values][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 [[https://golang.org/ref/spec#Comparison_operators][value equality]]. + +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 [[https://golang.org/ref/spec#Type_assertions][type assertion]] 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 +[[https://godoc.org/github.com/pkg/errors][github.com/pkg/errors]], +[[https://godoc.org/gopkg.in/errgo.v2][gopkg.in/errgo.v2]], +[[https://godoc.org/github.com/hashicorp/errwrap][github.com/hashicorp/errwrap]], +[[https://godoc.org/upspin.io/errors][upspin.io/errors]], +[[https://godoc.org/github.com/spacemonkeygo/errors][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 you 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 +[[https://golang.org/design/go2draft-error-printing][design draft for an optional method for richer 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 +[[https://go.googlesource.com/go/+/go1.12/src/compress/lzw/writer.go#209][`compress/lzw/writer.go`]] 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 +[[https://golang.org/design/go2draft-error-handling][presented a draft design]] +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 +[[https://golang.org/ref/spec#Defer_statements][`defer`]] 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 +[[https://golang.org/design/32437-try-builtin][a new design with three simplifications]]: +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 +[[https://github.com/golang/proposal/blob/master/design/go2draft-contracts.md][new design]], +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 “[[https://blog.golang.org/why-generics][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 +“[[https://groups.google.com/forum/#!msg/golang-nuts/8JFwR3ESjjI/cy7qZzN7Lw4J][an experiment in package installation]].” +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 [[https://golang.org/doc/faq#get_version][the Go FAQ]]: + +.html experiment/div-quote.html + +“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.” + +.html experiment/div-end.html + +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 +[[https://gopkg.in][`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 +[[https://github.com/go-yaml/yaml][go-yaml/yaml]] +has different APIs +in the v1 and v2 semantic version tags. +The `gopkg.in` server provides these with +different import paths +[[https://godoc.org/gopkg.in/yaml.v1][gopkg.in/yaml.v1]] +and +[[https://godoc.org/gopkg.in/yaml.v2][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: + +.html experiment/div-indent.html +.image experiment/yamldeps1.png _ 214 +.html experiment/div-end.html + +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: + +.html experiment/div-indent.html +.image experiment/yamldeps2.png _ 557 +.html experiment/div-end.html + +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: + +.html experiment/div-indent.html +.image experiment/yamldeps3.png _ 450 +.html experiment/div-end.html + +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_: + +.html experiment/div-quote.html + +“If an old package and a new package have the same import path, +the new package must be backwards compatible with the old package.” + +.html experiment/div-end.html + +*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 +[[https://godoc.org/go/build][`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, +[[https://godoc.org/golang.org/x/tools/go/packages][`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 [[https://godoc.org/golang.org/x/tools/go/packages][`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 [[https://docs.gomods.io][Athens project]] 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 [[https://groups.google.com/forum/#!topic/golang-announce/0wo8cOhGuAI][launched a proxy]] 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 [[https://golang.org/issue/new][filing a bug report]], +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 +[[https://langserver.org/][Language Server Protocol, LSP]], +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 + +.html experiment/div-indent.html +.image experiment/expsimp2.png _ 326 +.html experiment/div-end.html + +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. diff --git a/content/experiment/div-end.html b/content/experiment/div-end.html new file mode 100644 index 0000000..04f5b84 --- /dev/null +++ b/content/experiment/div-end.html @@ -0,0 +1 @@ +</div> diff --git a/content/experiment/div-indent.html b/content/experiment/div-indent.html new file mode 100644 index 0000000..627d871 --- /dev/null +++ b/content/experiment/div-indent.html @@ -0,0 +1 @@ +<div style="margin-left: 2em;"> diff --git a/content/experiment/div-quote.html b/content/experiment/div-quote.html new file mode 100644 index 0000000..191bfac --- /dev/null +++ b/content/experiment/div-quote.html @@ -0,0 +1 @@ +<div style="margin-left: 2em; font-style: italic;"> diff --git a/content/experiment/expsimp1.graffle b/content/experiment/expsimp1.graffle Binary files differnew file mode 100644 index 0000000..3dd4a98 --- /dev/null +++ b/content/experiment/expsimp1.graffle diff --git a/content/experiment/expsimp1.png b/content/experiment/expsimp1.png Binary files differnew file mode 100644 index 0000000..00f0709 --- /dev/null +++ b/content/experiment/expsimp1.png diff --git a/content/experiment/expsimp2.graffle b/content/experiment/expsimp2.graffle Binary files differnew file mode 100644 index 0000000..551ea3a --- /dev/null +++ b/content/experiment/expsimp2.graffle diff --git a/content/experiment/expsimp2.png b/content/experiment/expsimp2.png Binary files differnew file mode 100644 index 0000000..b94427e --- /dev/null +++ b/content/experiment/expsimp2.png diff --git a/content/experiment/yamldeps1.graffle b/content/experiment/yamldeps1.graffle Binary files differnew file mode 100644 index 0000000..48172e5 --- /dev/null +++ b/content/experiment/yamldeps1.graffle diff --git a/content/experiment/yamldeps1.png b/content/experiment/yamldeps1.png Binary files differnew file mode 100644 index 0000000..134be30 --- /dev/null +++ b/content/experiment/yamldeps1.png diff --git a/content/experiment/yamldeps2.graffle b/content/experiment/yamldeps2.graffle Binary files differnew file mode 100644 index 0000000..86e9586 --- /dev/null +++ b/content/experiment/yamldeps2.graffle diff --git a/content/experiment/yamldeps2.png b/content/experiment/yamldeps2.png Binary files differnew file mode 100644 index 0000000..ed3a1e5 --- /dev/null +++ b/content/experiment/yamldeps2.png diff --git a/content/experiment/yamldeps3.graffle b/content/experiment/yamldeps3.graffle Binary files differnew file mode 100644 index 0000000..6d5d188 --- /dev/null +++ b/content/experiment/yamldeps3.graffle diff --git a/content/experiment/yamldeps3.png b/content/experiment/yamldeps3.png Binary files differnew file mode 100644 index 0000000..439c23e --- /dev/null +++ b/content/experiment/yamldeps3.png |