Home > Articles > Programming

A Go Primer

  • Print
  • + Share This
This chapter is from the book

Implementing Interfaces

From: interface.go

5  type cartesianPoint struct{
6    x, y float64
7  }
8  type polarPoint struct {
9    r, θ float64
10 }
12 func (p cartesianPoint) X() float64 {return p.x }
13 func (p cartesianPoint) Y() float64 {return p.y }
14 func (p polarPoint) X() float64 {
15   return p.r*math.Cos(p.θ)
16 }
17 func (p polarPoint) Y() float64 {
18   return p.r*math.Sin(p.θ)
19 }
20 func (self cartesianPoint) Print() {
21   fmt.Printf("(%f, %f)\n", self.x, self.y)
22 }
23 func (self polarPoint) Print() {
24   fmt.Printf("(%f, %f°)\n", self.r, self.θ)
25 }
26 type Point interface{
27   Printer
28   X() float64
29   Y() float64
30 }
31 type Printer interface {
32   Print()
33 }

The dynamic dispatch mechanism in Go is reminiscent of StrongTalk, a strongly typed Smalltalk dialect. Interfaces describe a set of methods that a type understands. Unlike Java interfaces or Objective-C protocols, they do not need to be explicitly adopted.

Any type can be assigned to a variable with an interface type, as long as it implements all of the required methods. In some cases, this can be checked at compile time. For example, if one interface is a superset of another, then casting from the superset to the subset is always valid, as is casting from a structure type to an interface when the compiler sees that the structure implements the interface.

In other cases, it is not. These cases require a type assertion, detailed in the next section, which will generate a runtime panic if they fail. This means that any variable with an interface type is guaranteed to either be nil, or hold a valid value of a concrete type that implements the interface.

The example from the start of this section shows the creation of new structure types, and interfaces that they implement. Note that the structure can be defined before the interface. In fact, structures can be defined in entirely different packages to interfaces. This is especially useful if various third-party structures all implement the same method or group of methods: you can define a new interface that can be any one of them.

There are two interfaces declared in this example, both following Go naming conventions. The Printer interface defines a single method, so it follows the convention of appending the -er suffix to the method name to give the interface name.

The other interface uses interface composition to extend the Printer interface. This one defines an abstract data type. It provides methods for accessing the horizontal and vertical coordinates of a two-dimensional point. Interface composition is effectively equivalent to interface inheritance in Java. You can use it in some places where you would consider using single or multiple inheritance in other languages.

This example provides two structures that implement this interface, one using Cartesian and the other using polar coordinates. This is a simple example of how an interface can be used to hide the implementation. The two structures both start with lowercase letters, so they will not be exported from this package, while the interfaces will. You could extend this example by providing functions to construct a Point from polar and Cartesian coordinates, each returning one of a different kind of structure.

When you are dealing with interfaces, the distinction between methods that take pointers and ones that take values becomes more important. If you tried to assign an instance of this example structure to an interface that required the Log() method, then the assignment would be rejected. Assigning a pointer to an instance of this structure would work.

This seems counterintuitive. If you have a value, then you can always take its address to get a pointer, so why are the two method sets distinct? The answer is very simple: it helps avoid bugs. When you pass a value, you create a copy of a structure. When you pass a pointer, you alias the structure. If you pass a value and then implicitly, via method invocation on an interface, pass a pointer, then any changes that the method made would be made to the temporary copy, not to the original structure. This is probably not what you want, and if it is then you can just pass the pointer originally, rather than the copy.

The Go FAQ gives an example of a case where this could be problematic:

var buf bytes.Buffer
io.Copy(buf, os.Stdin)

The io.Copy() function copies data from something that implements the io.Reader interface to something that implements the io.Writer interface. When you call this function, it will pass a copy of buf as the first argument, because Go always passes by value, not by reference. It will then try to copy data from the standard input into the new copy of buf. When the function returns, the copy of buf will no longer be referenced, so the garbage collector will free it.

What the person writing this code probably wanted to do was copy data from the standard input into buf. The Go type system will reject this, because buf does not implement the io.Writer interface: the method for writing bytes to a buffer modifies the buffer and therefore requires a pointer receiver. By disallowing this, Go lets you get an error at compile time and trivially fix it by writing this instead:

var buf bytes.Buffer
io.Copy(&buf, os.Stdin)

If Go allowed values to use methods that are declared as requiring a pointer, then you would instead spend ages wondering why this line appeared to be reading the correct amount of data, but wasn’t storing any of it in the buffer that you declared. This is part of the Go philosophy of avoiding ambiguity. It just takes one extra character to make a pointer when you need one. That small amount of extra effort is a lot less than the time you’d spend debugging code where you meant one thing and Go assumed that you meant something else.

  • + Share This
  • 🔖 Save To Your Account