Interfaces

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.

The nil interface trap

I lost an hour today to an error value that was plainly nil and just as plainly refused to compare equal to nil.

The reduced version looked like this:

type problem struct{}

func (*problem) Error() string { return "problem" }

func check() error {
	var p *problem
	return p
}

fmt.Println(check() == nil) // false

My first theory involved compiler spite. The real explanation is less personal: an interface value contains a dynamic type and a dynamic value. check returns an interface containing *problem and a nil pointer. The pointer is nil, but the interface’s type word is not, so the interface itself is not nil.

Printing %#v made the situation clearer:

(*main.problem)(nil)

The fix was not reflection or a clever nil helper. It was to return a literal nil when there is no error:

func check() error {
	var p *problem
	if p == nil {
		return nil
	}
	return p
}

This matters most with error, because callers naturally write if err != nil. I now keep concrete pointer errors inside the function and only convert them to error on an actual failure. Interfaces are two-word descriptions, not magic boxes. Remembering that is cheaper than another hour of accusing the compiler.

Small Go interfaces

I wanted to test a function that wrote a report without letting the test create files. My first instinct, trained by larger object systems, was to invent a report hierarchy. Go let me stop much earlier.

The function only needs one operation:

type writer interface {
	Write([]byte) (int, os.Error)
}

func report(w writer, name string) os.Error {
	_, err := w.Write([]byte("hello, " + name + "\n"))
	return err
}

A file can satisfy that interface. So can a network connection or a little buffer in a test. None of those types has to announce that it implements writer; the method set is enough.

That last detail was my surprise. I am used to a type naming its interfaces at the type declaration. Here the relationship can be discovered where it is needed. The package containing report can define the narrow interface, even when the concrete type belongs to another package and cannot be edited.

The mechanism is structural. An interface describes a set of methods. A value is assignable to it when its type has those methods with the right signatures. The compiler checks the assignment, so this is not hopeful late binding. Calls through the interface still carry dynamic type information, but the agreement itself needs no explicit declaration.

Pointer methods add one wrinkle. If a method has a pointer receiver, a pointer to the type has that method; the plain value does not. That distinction matters when a method modifies state, and it explains several otherwise puzzling assignment errors.

I now prefer interfaces declared by the code that consumes them, and I keep them as small as the operation permits. A one-method interface is not a toy. It is often a precise seam between a useful function and everything it does not need to know.

The caveat is that an interface should represent behaviour, not merely hide every concrete type on principle. Returning an interface too early can discard useful methods and make code harder to follow. If there is only one implementation and no boundary to protect, the concrete type is frequently clearer.

Here, one method bought a test with no temporary file and no elaborate framework. I will take that trade.

Replacing Signals With Small Interfaces

I ported the non-visual half of a Qt utility to Go and immediately missed signals and slots. They had been carrying progress, log messages, and completion notifications between objects. My first replacement was a channel for every event. It worked and looked like a telephone exchange designed by an enthusiastic octopus.

The simpler answer for synchronous notifications was a small interface:

type Progress interface {
    Step(done, total int)
}

func scan(files []string, progress Progress) os.Error {
    for i, name := range files {
        if err := scanFile(name); err != nil {
            return err
        }
        progress.Step(i+1, len(files))
    }
    return nil
}

The terminal implementation prints a line. A test implementation records calls. A future graphical shell can arrange delivery to its UI thread. The scanner knows only the behavior it needs.

This differs from a Qt signal in an important way: the call is ordinary and synchronous. The scanner waits while Step runs. That makes control flow and failure behavior obvious, but a slow observer slows the operation. If isolation is required, an adapter can implement Progress by sending values to a channel consumed elsewhere.

I prefer putting that asynchronous decision in the adapter rather than in the core interface. A channel everywhere turns every notification into a lifetime and shutdown problem. An interface everywhere can accidentally block. Neither mechanism removes design; they merely make different design visible.

For optional progress I use a no-op implementation rather than scattering nil checks through the scanner. It is one tiny type with one empty method, and it keeps the hot path unsurprising.

Completion remains a return, not another notification. The caller already has a direct control path, and duplicating it creates two accounts of whether work finished.

The Go compiler decides satisfaction structurally. My terminal reporter does not inherit from a framework base or declare that it implements Progress; matching Step is enough. That made extracting this contract from existing code much easier than I expected.

This is written against an early 2011 weekly snapshot, including the os.Error return convention. APIs will move. The lesson I am keeping is narrower: use a direct small interface for required behavior, then add a channel at the boundary where asynchronous delivery is actually needed. Not every callback needs its own postal service.

Interfaces After Qt

Qt taught me to appreciate explicit contracts. It also taught me that an object system can acquire enough machinery to qualify for municipal government.

Go’s interfaces are much smaller. A type satisfies an interface by having the required methods; it does not announce the relationship in its declaration. This took a moment to trust because I am used to inheritance lists serving as signposts.

Here is the complete contract for something that can write bytes in my current snapshot:

type Writer interface {
    Write(p []byte) (n int, err os.Error)
}

A file can satisfy it. So can a network connection or a tiny test buffer. The producer only needs to accept a Writer:

func emit(w Writer, p []byte) os.Error {
    _, err := w.Write(p)
    return err
}

There is no implements Writer marker on those concrete types. The advantage is that I can define an interface around behavior already provided by a package. The package author did not need to predict my abstraction.

The cost is discoverability. Looking at a type alone does not list every interface it happens to satisfy, and a method signature changed by one type is a different method. Compiler diagnostics are doing more of the navigation work than they do in my C++ code.

I tried the idea on a log collector. The original Python version accepted anything with a write method because Python accepts almost anything until it does not. The Go version gets similar flexibility, but verifies the method set when values are connected. That is a useful middle ground.

Interfaces also contain a pair of things internally: a dynamic type and a dynamic value. An interface value can therefore be non-empty even when the pointer stored inside it is nil. I managed to manufacture that surprise within an hour, which must be some kind of efficiency record.

These details reflect a weekly build, not a stable release. Method-set and library conventions may still shift. Even so, structural satisfaction feels important. It encourages narrow contracts at the point of use rather than grand interface families designed before the program has done anything useful.