Blog

The two words inside an interface

I changed a formatter from a concrete parameter to an interface and expected the call to behave like a generic wrapper around the value. The program worked, but looking at method dispatch made the cost and behavior less mysterious.

Conceptually, an interface value is two machine words: information describing the dynamic type and a word referring to the dynamic value. For a non-empty interface, the type information also leads to the method table needed to dispatch calls. An empty interface needs no method table, but it still carries dynamic type and value.

Copying an interface copies those two words, not the object they describe:

var src io.Reader = bytes.NewBufferString("hello")
dst := src

Both interfaces refer to the same buffer. Reading through dst advances the state later observed through src; the assignment did not clone the buffer. This is easy to miss because the interface itself is a small value while the dynamic value may be shared mutable state.

Calls through an interface use the implementation selected by the dynamic type. Go’s implicit satisfaction means the concrete type does not declare which interfaces it implements; the compiler checks the method set at assignment or call sites. Pointer and value method sets still matter, so T and *T need not satisfy the same interface.

The two-word representation does not promise a two-word operation. A call still dispatches to the dynamic type’s method, and whether a value escapes depends on how the compiler can follow it through that call. Interface size alone says little about allocation.

An interface-to-interface assignment can change the static contract while preserving the same dynamic value. A type assertion asks whether that dynamic type satisfies another interface; it does not convert the object into a new representation. I use the two-result form when failure is ordinary:

c, ok := w.(io.Closer)

The one-result form panics on failure, which belongs only where the invariant is truly guaranteed.

I kept the interface where it represented a real behavioral boundary and restored the concrete parameter in the private formatter. Interfaces are contracts, not performance annotations or automatic copies of the values inside them.

GOPATH is not a versioning system

I reproduced a build failure only after deleting my GOPATH. That sentence contains most of the problem.

My normal workspace had this shape:

$GOPATH/
    src/example.com/service
    src/github.com/some/dependency
    pkg/darwin_amd64
    bin

go get fetched the dependency’s current source into the expected import path. My machine had an older checkout that still provided a function the service used. A fresh machine fetched a newer revision where the function had changed. The import path named a repository location, not the exact source revision I had tested.

I briefly considered copying dependencies under a random directory in the service. In 2014 the go command does not define vendoring semantics that make such a copy transparently override the canonical import. Rewriting imports to local copies creates new package identities and a permanent maintenance chore.

For deployable applications, I record the repository revisions and make the build checkout those revisions into an isolated GOPATH. Some teams use scripts; others use emerging third-party dependency tools. None is yet the universal answer. Libraries have an even harder choice because pinning their transitive world can conflict with the application assembling the final program.

I also keep import paths canonical. Relative imports make a package depend on where it happens to sit and behave poorly outside a narrow local experiment.

The decisive experiment was a new empty workspace:

$ mkdir /tmp/service-build
$ GOPATH=/tmp/service-build go get example.com/service/cmd/server

That made every source checkout visible under one disposable root. I recorded git rev-parse HEAD for each dependency and reproduced the working set by checking out those revisions. The script fails if a revision cannot be fetched; silently falling back to a branch tip would restore the original uncertainty.

Import-path compatibility remains social rather than solver-enforced. If upstream makes an incompatible change at the same path, the go command cannot select “the old API” from the import statement. Forking under a new path changes package identity, so values from original and forked packages are distinct even when declarations match.

For every recorded revision I also keep its canonical repository URL. Import paths can redirect discovery, and repeatability requires knowing both what commit was selected and where the build expects to obtain it.

GOPATH solves workspace layout and import discovery well. It does not solve repeatable dependency selection. Saying that plainly avoids a lot of mystical advice about cleaning $GOPATH/pkg. My build was not stale; its input was unspecified.

More on validating with Go

A few days ago I posted about a new Go package called validator that we initially developed for our own internal use at project7.io but what fun is internal stuff, right? So we opensourced it.

Then Matthew Holt pointed me to an ongoing discussion
on validation happening on martini-contrib’s github. By the way, if you don’t know martini yet, go rectify that right now.

And today I learned about check, which takes a completely different
approach to data validation than the one we took, which by the way is totally fine and you should check it out.

Good to know we were not the only ones with the problem. Although I’m happy with how our package turned out, I kind of wish
I had found these other guys earlier. Could have shared some code or just ideas.

[ANN] Package validator

The thing about working in a startup under stealth mode is you can’t often talk about what you’re doing. Thankfully, from time to time, an opportunity appears that lets us at least share something tangential.

A large part of our project at project7.io involves receiving data from a client (generally in JSON) for processing. This involves unmarshaling the JSON into a struct, something that Go does very well. But then comes the boring part: checking that the information sent from the client is correct and complete before doing anything with it.

The boring life of validating stuff

if t.Username != "" && t.Age > 18 && t.Foo != nil && len(t.Bar.Baz) > 8 && ...

We had to do this often and for a large number of different structs and sometimes a struct gained a new field and we had to go back and see that it was being properly validated everywhere. It was so boring that we ended up writing something to make it easier for us. We’ve been using this for a while and now we decided to open source it in the hopes that it might be useful for others.

Package validator implements validation of variables. Initially we had implemented JSONschema but we don’t always deal with JSON, we also get data as XML and sometimes as form-encoded. So we changed our approach and went right to the struct definitions.

A struct definition with some validation rules attached

type T struct {
  A int    `validate:"nonzero"`
  B string `validate:"nonzero,max=10"`
  C struct {
      Ca int    `validate:"min=5,max=10"`
      Cb string `validate:"min=8, regexp:^[a-zA-Z]+"`
  }
}

This allowed us to attach validation rules right to the data structure definitions. Then instead of large, boring list of if statements, we were able to validate in single function call.

Validating an instance of a struct

if valid, _ := validator.Validate(t); !valid {
  // not valid, so return an http.StatusBadRequest of something
}

Multiple rules for different situations

We often use the same struct to deal with different data coming from the client. Sometimes we only care about one or two of the fields in one scenario, so we also supported multiple rules like, say

A struct with two different sets of rules

type T struct {
  A int    `foo:"nonzero" bar:"min=5,max=10"`
  B string `bar:"nonzero"`
}

In scenario foo, we need A to be non-zero, but we don’t care for what is in B. In the bar scenario, however, we need B to be non-zero and the value of A to be between 5 and 10, inclusive. To validate, we then make validator use a different tag name for each case

WithTag() FTW

t := T{A: 3}
validator.WithTag("foo").Validate(t) // valid
validator.WithTag("bar").Validate(t) // invalid

We can also change the tag by using SetTag with will then make the tag name persistent until changed again by another call to SetTag.

Please refer to http://godoc.org/gopkg.in/validator.v1 for a lot more documentation, use cases, and how to access the individual validation errors found for each field.

Small Go packages and boring APIs

I split a package this week because its name had become meaningless. util contained HTTP parsing, identifiers, retry logic, and one function that formatted dates. Importing it told a reader only that I had eventually run out of nouns.

My first repair was worse: one package per file. That produced retry, identifier, httputil, and dateutil, each with one exported function and several awkward dependencies. Package boundaries are not decorative folders. They should group a coherent capability and hide its implementation.

The useful split placed request parsing beside the server code that owns its rules. Identifier generation became a small package because several unrelated commands use the same format. Date formatting stayed with the output package that defines that presentation.

I also removed exported names. This API:

type RetryManager struct {
	RetryCount int
	RetryDelay time.Duration
}

func NewRetryManager(count int, delay time.Duration) *RetryManager
func (r *RetryManager) ExecuteRetryOperation(f func() error) error

became:

func Retry(n int, delay time.Duration, f func() error) error

There was no durable manager state and only one operation. The type existed because objects felt respectable. The function is harder to misuse, easier to test, and gives me fewer compatibility promises.

Exported identifiers are the public surface, even before a package reaches version one. Renaming one breaks every importer. Adding a method to an interface also breaks implementations outside the package, so I prefer accepting the smallest interface at the point of use rather than publishing a large “service” interface beside a concrete type.

Package names deserve the same economy. Callers write the package qualifier, so httpclient.Client stutters while client.Client or a more specific package often reads naturally. I avoid common, base, and misc; they attract unrelated code through gravity rather than design.

The test after the reorganization was not just go test ./.... I opened several call sites and read them without the implementation. If id.New() and server.ParseRequest(r) communicated enough, the boundary was doing work. If understanding required knowing which former utility file held the function, I had merely rearranged shelves.

Errors are part of the surface too. Callers should not need to compare prose. When they must distinguish a condition, I expose a stable sentinel error or a small concrete error type and document it. Most internal errors remain application details rather than becoming a taxonomy the package must support forever.

Small packages are useful. Tiny packages are not automatically virtuous. I now split around ownership and stable concepts, not line count. That gives me APIs boring enough that I can return to the actual program.