Chapter 7. Interfaces¶
Interface types express generalizations or abstractions about the behaviors of other types. By generalizing, interfaces facilitate more flexible and adaptable functions because they are not tied to the details of one particular implementation.
Different from the notion of interfaces in other object-oriented languages, Go's interfaces is distinctive in that they are satisfied implicitly. In other words, there's no need to declare all the interfaces that a given concrete type satisfies; simply possessing the necessary methods is enough. With this design, you are able to create new interfaces that are satisfied by existing concrete types without changing the existing types, which is particularly useful for types defined in packages that you don't control.
This chapter discusses:
- Basic mechanics of interface types and their values
- Several important interfaces from the standard library
- Type assertions and type switches
Interfaces as Contracts¶
Concrete type and interface type *¶
All the types discussed so far are concrete types. A concrete type specifies the exact representation of its values and exposes the intrinsic operations of that representation, such as arithmetic for numbers, or indexing, append, and range for slices. A concrete type may also provide additional behaviors through its methods. When you have a value of a concrete type, you know exactly what it is and what you can do with it.
There is another kind of type in Go called an interface type. An interface is an abstract type. It doesn't expose the representation or internal structure of its values, or the set of basic operations they support; it reveals only some of their methods. When you have a value of an interface type, you know nothing about what it is; you know only what it can do, or more precisely, what behaviors are provided by its methods.
The io.Writer interface *¶
This book has used two similar functions for string formatting:
fmt.Printf, which writes the result to the standard output (a file)fmt.Sprintf, which returns the result as a string
Thanks to interfaces, both of these functions are in effect wrappers around a third function, fmt.Fprintf, which is agnostic about what happens to the result it computes:
package fmt func Fprintf(w io.Writer, format string, args ...interface{}) (int, error) func Printf(format string, args ...interface{}) (int, error) { return Fprintf(os.Stdout, format, args...) } func Sprintf(format string, args ...interface{}) string { var buf bytes.Buffer Fprintf(&buf, format, args...) return buf.String() }
The F prefix of Fprintf stands for file and indicates that the formatted output should be written to the file provided as the first argument.
- In the
Printfcase, the argument,os.Stdout, is an*os.File. - In the
Sprintfcase, the argument is not a file but superficially resembles one:&bufis a pointer to a memory buffer to which bytes can be written.
The first parameter of Fprintf is not a file either. It's an io.Writer, which is an interface type with the following declaration:
package io // Writer is the interface that wraps the basic Write method. // // Write writes len(p) bytes from p to the underlying data stream. // It returns the number of bytes written from p (0 <= n <= len(p)) // and any error encountered that caused the write to stop early. // Write must return a non-nil error if it returns n < len(p). // Write must not modify the slice data, even temporarily. // // Implementations must not retain p. type Writer interface { Write(p []byte) (n int, err error) }
The io.Writer interface defines the contract between Fprintf and its callers.
- The contract requires that the caller provide a value of a concrete type like
*os.Fileor*bytes.Bufferthat has a method calledWritewith the appropriate signature and behavior. - The contract guarantees that
Fprintfwill do its job given any value that satisfies theio.Writerinterface. Fprintfmay not assume that it is writing to a file or to memory, only that it can callWrite.
Because fmt.Fprintf assumes nothing about the representation of the value and relies only on the behaviors guaranteed by the io.Writer contract, we can safely pass a value of any concrete type that satisfies io.Writer as the first argument to fmt.Fprintf. This freedom to substitute one type for another that satisfies the same interface is called substitutability, and is a hallmark of object-oriented programming.
The following example uses a new type. The Write method of the *ByteCounter type below merely counts the bytes written to it before discarding them. (The conversion is required to make the types of len(p) and *c match in the += assignment statement.)
gopl.io/ch7/bytecounter/main.go
type ByteCounter int func (c *ByteCounter) Write(p []byte) (int, error) { *c += ByteCounter(len(p)) // convert int to ByteCounter return len(p), nil }
Since *ByteCounter satisfies the io.Writer contract, we can pass it to Fprintf; the ByteCounter correctly accumulates the length of the result.
var c ByteCounter c.Write([]byte("hello")) fmt.Println(c) // "5", = len("hello") c = 0 // reset the counter var name = "Dolly" fmt.Fprintf(&c, "hello, %s", name) fmt.Println(c) // "12", = len("hello, Dolly")
The fmt.Stringer interface *¶
Besides io.Writer, fmt.Stringer is another interface of great importance to the fmt package. Fprintf and Fprintln provide a way for types to control how their values are printed. For example:
- In Section 2.5, we defined a
Stringmethod for theCelsiustype so that temperatures would print as "100°C. - In Section 6.5, we equipped
*IntSetwith aStringmethod so that sets would be rendered using traditional set notation like "{1 2 3}".
Declaring a String method makes a type satisfy fmt.Stringer, which is one of the most widely used interfaces of all:
package fmt // The String method is used to print values passed // as an operand to any format that accepts a string // or to an unformatted printer such as Print. type Stringer interface { String() string }
Section 7.10 will explain how the fmt package discovers which values satisfy this interface.
Interface Types¶
An interface type specifies a set of methods that a concrete type must possess to be considered an instance of that interface.
The io.Writer type is one of the most widely used interfaces because it provides an abstraction of all the types to which bytes can be written, such as files, memory buffers, network connections, HTTP clients, archivers and hashers. The io package defines many other useful interfaces. A Reader represents any type from which you can read bytes, and a Closer is any value that you can close, such as a file or a network connection. (Notice the naming convention for many of Go's single-method interfaces.)
package io type Reader interface { Read(p []byte) (n int, err error) } type Closer interface { Close() error }
The following are examples of new interface types as combinations of existing ones:
type ReadWriter interface { Reader Writer } type ReadWriteCloser interface { Reader Writer Closer }
The syntax used above, which resembles struct embedding, enables us to name another interface as a shorthand for writing out all of its methods. This is called embedding an interface. We could have written io.ReadWriter without embedding like this:
type ReadWriter interface { Read(p []byte) (n int, err error) Write(p []byte) (n int, err error) }
We can even use a mixture of the two styles:
type ReadWriter interface { Read(p []byte) (n int, err error) Writer }
All three declarations have the same effect. The order in which the methods appear is immaterial. All that matters is the set of methods.
Interface Satisfaction¶
A type satisfies an interface if it possesses all the methods the interface requires. For example:
- An
*os.Filesatisfiesio.Reader,Writer,Closer, andReadWriter. - A
*bytes.BuffersatisfiesReader,Writer, andReadWriter, but does not satisfyCloserbecause it does not have aClosemethod.
As a shorthand, Go programmers often say that a concrete type "is a" particular interface type, meaning that it satisfies the interface. For example:
- A
*bytes.Bufferis anio.Writer. - An
*os.Fileis anio.ReadWriter.
The assignability rule (Section 2.4.2) for interfaces is very simple: an expression may be assigned to an interface only if its type satisfies the interface. For example:
var w io.Writer w = os.Stdout // OK: *os.File has Write method w = new(bytes.Buffer) // OK: *bytes.Buffer has Write method w = time.Second // compile error: time.Duration lacks Write method var rwc io.ReadWriteCloser rwc = os.Stdout // OK: *os.File has Read, Write, Close methods rwc = new(bytes.Buffer) // compile error: *bytes.Buffer lacks Close method
This rule applies even when the right-hand side is itself an interface:
w = rwc // OK: io.ReadWriteCloser has Write method rwc = w // compile error: io.Writer lacks Close method
We should explain one subtlety in what it means for a type to have a method. Recall from Section 6.2 that for each named concrete type T, some of its methods have a receiver of type T itself whereas others require a *T pointer. Recall also that it is legal to call a *T method on an argument of type T as long as the argument is a variable; the compiler implicitly takes its address. However, this is mere syntactic sugar: a value of type T does not possess all the methods that a *T pointer does; as a result, T might satisfy fewer interfaces.
For example, the String method of the IntSet type from Section 6.5 requires a pointer receiver, so we cannot call that method on a non-addressable IntSet value:
type IntSet struct { /* ... */ } func (*IntSet) String() string var _ = IntSet{}.String() // compile error: String requires *IntSet receiver
But we can call it on an IntSet variable:
var s IntSet var _ = s.String() // OK: s is a variable and &s has a String method
However, since only *IntSet has a String method, only *IntSet satisfies the fmt.Stringer interface:
var _ fmt.Stringer = &s // OK var _ fmt.Stringer = s // compile error: IntSet lacks String method
Section 12.8 includes a program that prints the methods of an arbitrary value, and the godoc -analysis=type tool (Section 10.7.4) displays the methods of each type and the relationship between interfaces and concrete types.
An interface wraps and conceals the concrete type and value that it holds. Only the methods revealed by the interface type may be called, even if the concrete type has others:
os.Stdout.Write([]byte("hello")) // OK: *os.File has Write method os.Stdout.Close() // OK: *os.File has Close method var w io.Writer w = os.Stdout w.Write([]byte("hello")) // OK: io.Writer has Write method w.Close() // compile error: io.Writer lacks Close method
Empty interface: interface{} *¶
An interface with more methods, such as io.ReadWriter, tells us more about the values it contains, and places greater demands on the types that implement it, than does an interface with fewer methods such as io.Reader. Similarly, the type interface{}, which has no methods at all, tell us about nothing about the concrete types that satisfy it. This may seem useless, but in fact the type interface{}, which is called the empty interface type, is indispensable. Because the empty interface type places no demands on the types that satisfy it, we can assign any value to the empty interface.
var any interface{} any = true any = 12.34 any = "hello" any = map[string]int{"one": 1} any = new(bytes.Buffer)
The empty interface type has been used in the very first example in this book, because it is what allows functions like fmt.Println, or errorf in Section 5.7, to accept arguments of any type.
Having created an interface{} value containing a boolean, float, string, map, pointer, or any other type, we can do nothing directly to the value it holds since the interface has no methods. We need a way to get the value back out again, which can be done using a type assertion, discussed in Section 7.10.
Since interface satisfaction depends only on the methods of the two types involved, there is no need to declare the relationship between a concrete type and the interfaces it satisfies. However, it is occasionally useful to document and assert the relationship when it is intended but not otherwise enforced by the program. The declaration below asserts at compile time that a value of type *bytes.Buffer satisfies io.Writer:
// *bytes.Buffer must satisfy io.Writer var w io.Writer = new(bytes.Buffer)
We needn't allocate a new variable since any value of type *bytes.Buffer will do, even nil, which we write as (*bytes.Buffer)(nil) using an explicit conversion. Since we never intend to refer to w, we can replace it with the blank identifier, which makes more frugal variant:
// *bytes.Buffer must satisfy io.Writer var _ io.Writer = (*bytes.Buffer)(nil)
Non-empty interface types such as io.Writer are most often satisfied by a pointer type, particularly when one or more of the interface methods implies some kind of mutation to the receiver, as the Write method does. A pointer to a struct is an especially common method-bearing type.
Pointer types are not the only types that satisfy interfaces, and even interfaces with mutator methods may be satisfied by other reference types. For example:
- Slice types with methods (
geometry.Path, Section 6.1) - Map types with methods (
url.Values, Section 6.2.1), - Function type with methods (
http.HandlerFunc, Section 7.7). - Even basic types may satisfy interfaces: in Section 7.4,
time.Durationsatisfiesfmt.Stringer.
A concrete type may satisfy many unrelated interfaces. For example, a program that organizes or sells digitized cultural artifacts might define the following set of concrete types:
Album Book Movie Magazine Podcast TVEpisode Track
Each abstraction of interest can be expressed as an interface. Some properties are common to all artifacts, such as a title, a creation date, and a list of creators (authors or artists).
type Artifact interface { Title() string Creators() []string Created() time.Time }
Other properties are restricted to certain types of artifacts. Properties of the printed word are relevant only to books and magazines, whereas only movies and TV episodes have a screen resolution.
type Text interface { Pages() int Words() int PageSize() int } type Audio interface { Stream() (io.ReadCloser, error) RunningTime() time.Duration Format() string // e.g., "MP3", "WAV" } type Video interface { Stream() (io.ReadCloser, error) RunningTime() time.Duration Format() string // e.g., "MP4", "WMV" Resolution() (x, y int) }
These interfaces are not the only useful way to group related concrete types together and express the facets they share in common. We may discover other groupings later. For example, if we find we need to handle Audio and Video items in the same way, we can define a Streamer interface to represent their common aspects without changing any existing type declarations.
type Streamer interface { Stream() (io.ReadCloser, error) RunningTime() time.Duration Format() string }
Each grouping of concrete types based on their shared behaviors can be expressed as an interface type. Unlike class-based languages, in which the set of interfaces satisfied by a class is explicit, in Go we can define new abstractions or groupings of interest when we need them, without modifying the declaration of the concrete type. This is particularly useful when the concrete type comes from a package written by a different author. Of course, there do need to be underlying commonalities in the concrete types.
Parsing Flags with flag.Value¶
This section discusses another standard interface, flag.Value, which enables us to define new notations for command-line flags. For example, the following program sleeps for a specified period of time:
var period = flag.Duration("period", 1*time.Second, "sleep period") func main() { flag.Parse() fmt.Printf("Sleeping for %v...", *period) time.Sleep(*period) fmt.Println() }
Before it goes to sleep it prints the time period. The fmt package calls the time.Duration's String method to print the period in a user-friendly notation instead of a number of nanoseconds:
$ go build gopl.io/ch7/sleep $ ./sleep Sleeping for 1s...
- By default, the sleep period is one second, but it can be controlled through the
-periodcommand-line flag. - The
flag.Duratiofunction creates a flag variable of typetime.Durationand allows the user to specify the duration in a variety of user-friendly formats, including the same notation printed by theStringmethod. This symmetry of design leads to a nice user interface.
$ ./sleep -period 50ms Sleeping for 50ms... $ ./sleep -period 2m30s Sleeping for 2m30s... $ ./sleep -period 1.5h Sleeping for 1h30m0s... $ ./sleep -period "1 day" invalid value "1 day" for flag -period: time: invalid duration 1 day
It's easy to define new flag notations for our own data types. We need only define a type that satisfies the flag.Value interface, whose declaration is:
package flag // Value is the interface to the value stored in a flag. type Value interface { String() string Set(string) error }
- The
Stringmethod formats the flags' value for use in command-line help messages; thus everyflag.Valueis also afmt.Stringer. - The
Setmethod parses its string argument and updates the flag value. In effect, theSetmethod is the inverse of theStringmethod, and it is good practice for them to use the same notation.
The following example defines a celsiusFlag type that allows a temperature to be specified in Celsius, or in Fahrenheit with an appropriate conversion. Notice that celsiusFlag embeds a Celsius (Section 2.5), thereby getting a String method for free. To satisfy flag.Value, we need only declare the Set method:
gopl.io/ch7/tempconv/tempconv.go
type celsiusFlag struct{ Celsius } func (f *celsiusFlag) Set(s string) error { var unit string var value float64 fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed switch unit { case "C", "°C": f.Celsius = Celsius(value) return nil case "F", "°F": f.Celsius = FToC(Fahrenheit(value)) return nil } return fmt.Errorf("invalid temperature %q", s) }
The call to fmt.Sscanf parses a floating-point number (value) and a string (unit) from the input s. Although one must usually check Sscanf's error result, in this case we don't need to because if there was a problem, no switch case will match.
The CelsiusFlag function below wraps it all up.
// CelsiusFlag defines a Celsius flag with the specified name, // default value, and usage, and returns the address of the flag variable. // The flag argument must have a quantity and a unit, e.g., "100C". func CelsiusFlag(name string, value Celsius, usage string) *Celsius { f := celsiusFlag{value} flag.CommandLine.Var(&f, name, usage) return &f.Celsius }
- To the caller, it returns a pointer to the
Celsiusfield embedded within thecelsiusFlagvariablef. TheCelsiusfield is the variable that will be updated by theSetmethod during flags processing. - The call to
Varadds the flag to the application's set of command-line flags, the global variableflag.CommandLine. Programs with complex command-line interfaces may have several variables of this type. - The call to
Varassigns a*celsiusFlagargument to aflag.Valueparameter, causing the compiler to check that*celsiusFlaghas the necessary methods. (See Section 7.3)
The following program uses CelsiusFlag:
gopl.io/ch7/tempflag/tempflag.go
var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature")
func main() {
flag.Parse()
fmt.Println(*temp)
}
Run this program:
$ go build gopl.io/ch7/tempflag $ ./tempflag 20°C $ ./tempflag -temp -18C -18°C $ ./tempflag -temp 212°F 100°C $ ./tempflag -temp 273.15K invalid value "273.15K" for flag -temp: invalid temperature "273.15K" Usage of ./tempflag: -temp value the temperature (default 20°C) $ ./tempflag -help Usage of ./tempflag: -temp value the temperature (default 20°C)
Interface Values¶
Conceptually, an interface value (a value of an interface type) has two components:
- A concrete type, called the interface's dynamic type
- A value of that type, called the interface's dynamic value
For a statically typed language like Go, types are a compile-time concept, so a type is not a value. In our conceptual model, a set of values called type descriptors provide information about each type, such as its name and methods. In an interface value, the type component is represented by the appropriate type descriptor.
In the four statements below, the variable w takes on three different values. (The initial and final values are the same.)
var w io.Writer w = os.Stdout w = new(bytes.Buffer) w = nil
The first statement declares w:
var w io.Writer
In Go, variables are always initialized to a well-defined value, and interfaces are no exception. The zero value for an interface has both its type and value components set to nil (see the figure below).
An interface value is described as nil or non-nil based on its dynamic type, so this is a nil interface value. You can test whether an interface value is nil using w == nil or w != nil. Calling any method of a nil interface value causes a panic:
w.Write([]byte("hello")) // panic: nil pointer dereference
The second statement assigns a value of type *os.File to w:
w = os.Stdout
This assignment involves an implicit conversion from a concrete type to an interface type, and is equivalent to the explicit conversion io.Writer(os.Stdout). Such conversion, whether explicit or implicit, captures the type and the value of its operand. The interface value's dynamic type is set to the type descriptor for the pointer type *os.File, and its dynamic value holds a copy of os.Stdout, which is a pointer to the os.File variable representing the standard output of the process (see the figure below).
Calling the Write method on an interface value containing an *os.File pointer causes the (*os.File).Write method to be called, which prints "hello".
w.Write([]byte("hello")) // "hello"
In general, we cannot know at compile time what the dynamic type of an interface value will be, so a call through an interface must use dynamic dispatch. Instead of a direct call, the compiler must generate code to obtain the address of the method named Write from the type descriptor, then make an indirect call to that address. The receiver argument for the call is a copy of the interface's dynamic value,os.Stdout. The effect is as if we had made this call directly:
os.Stdout.Write([]byte("hello")) // "hello"
The third statement assigns a value of type *bytes.Buffer to the interface value:
w = new(bytes.Buffer)
The dynamic type is now *bytes.Buffer and the dynamic value is a pointer to the newly allocated buffer (see figure below).
A call to the Write method uses the same mechanism as before:
w.Write([]byte("hello")) // writes "hello" to the bytes.Buffer
This time, the type descriptor is *bytes.Buffer, so the (*bytes.Buffer).Write method is called, with the address of the buffer as the value of the receiver parameter. The call appends "hello" to the buffer.
Finally, the fourth statement assigns nil to the interface value:
w = nil
This resets both its components to nil, restoring w to the same state as when it was declared, as shown in Figure 7.1.
An interface value can hold arbitrarily large dynamic values. For example, the time.Time
type is a struct type with several unexported fields.
var x interface{} = time.Now()
This interface value is like the following figure:
Conceptually, the dynamic value always fits inside the interface value, no matter how large its type. Note that this is only a conceptual model; a realistic implementation is quite different.
Interface values may be compared using == and !=. Two interface values are equal if either of the following occurs:
- Both of them are nil.
- Their dynamic types are identical and their dynamic values are equal according to the usual behavior of
==for that type.
Because interface values are comparable, they may be used as the keys of a map or as the operand of a switch statement. However, if two interface values are compared and have the same dynamic type, but that type is not comparable (e.g. slice), then the comparison fails with a panic:
var x interface{} = []int{1, 2, 3} fmt.Println(x == x) // panic: comparing uncomparable type []int
Therefore, interface types are unusual: interface values are comparable, but may panic, while other types are either safely comparable or not comparable at all:
- Safely comparable, such as basic types and pointers
- Not comparable, such as slices, maps, and functions
When comparing interface values or aggregate types that contain interface values, we must be aware of the potential for a panic. A similar risk exists when using interfaces as map keys or switch operands. Only compare interface values if you are certain that they contain dynamic values of comparable types.
When handling errors, or during debugging, it is often helpful to report the dynamic type of an interface value, using the fmt package's %T verb:
var w io.Writer fmt.Printf("%T\n", w) // "<nil>" w = os.Stdout fmt.Printf("%T\n", w) // "*os.File" w = new(bytes.Buffer) fmt.Printf("%T\n", w) // "*bytes.Buffer"
Internally, fmt uses reflection (Chapter 12) to obtain the name of the interface's dynamic type.
Caveat: An Interface Containing a Nil Pointer Is Non-Nil¶
A nil interface value, which contains no value at all, is not the same as an interface value containing a pointer that happens to be nil.
In the program below, when debug is set to true, the function collects the output of the function f in a bytes.Buffer.
const debug = true func main() { var buf *bytes.Buffer if debug { buf = new(bytes.Buffer) // enable collection of output } f(buf) // NOTE: subtly incorrect! if debug { // ...use buf... } } // If out is non-nil, output will be written to it. func f(out io.Writer) { // ...do something... if out != nil { out.Write([]byte("done!\n")) } }
We might expect that changing debug to false would disable the collection of the output, but in fact it causes the program to panic during the out.Write call:
if out != nil { out.Write([]byte("done!\n")) // panic: nil pointer dereference }
When main calls f, it assigns a nil pointer of type *bytes.Buffer to the out parameter, so the dynamic value of out is nil. However, its dynamic type is *bytes.Buffer, meaning that out is a non-nil interface containing a nil pointer value (see figure below), so the defensive check out != nil is still true.
The dynamic dispatch mechanism determines that (*bytes.Buffer).Write must be called but this time with a receiver value that is nil. For some types, such as *os.File, nil is a valid receiver (Section 6.2.1), but *bytes.Buffer is not among them. The method is called, but it panics as it tries to access the buffer.
The problem is that although a nil *bytes.Buffer pointer has the methods needed to satisfy the interface, it doesn't satisfy the behavioral requirements of the interface. In particular, the call violates the implicit precondition of (*bytes.Buffer).Write that its receiver is not nil, so assigning the nil pointer to the interface was a mistake. The solution is to change the type of buf in main to io.Writer, thereby avoiding the assignment of the dysfunctional value to the interface in the first place:
var buf io.Writer if debug { buf = new(bytes.Buffer) // enable collection of output } f(buf) // OK
Sorting with sort.Interface¶
Sorting is a frequently used operation in many programs. [p186]
In many languages, the sorting algorithm is associated with the sequence data type, while the ordering function is associated with the type of the elements.
In Go, the sort package provides in-place sorting of any sequence according to any ordering function. However, its design is rather unusual:
sort.Sortfunction assumes nothing about the representation of either the sequence or its elements. Instead, it uses an interface,sort.Interface, to specify the contract between the generic sort algorithm and each sequence type that may be sorted.- An implementation of this interface determines both the concrete representation of the sequence, which is often a slice, and the desired ordering of its elements.
An in-place sort algorithm needs three things:
- The length of the sequence
- A means of comparing two elements
- A way to swap two elements
They are the three methods of sort.Interface:
package sort type Interface interface { Len() int Less(i, j int) bool // i, j are indices of sequence elements Swap(i, j int) }
To sort any sequence, we need to define a type that implements these three methods, then apply sort.Sort to an instance of that type. For example, consider sorting a slice of strings. The new type StringSlice and its Len, Less, and Swap methods are shown below:
type StringSlice []string func (p StringSlice) Len() int { return len(p) } func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] } func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
Now we can sort a slice of strings, names, by converting the slice to a StringSlice like this:
sort.Sort(StringSlice(names))
The conversion yields a slice value with the same length, capacity, and underlying array as names but with a type that has the three methods required for sorting.
Sorting a slice of strings is so common that the sort package provides the StringSlice type and a function called Strings. The call above can be simplified as:
sort.Strings(names)
This technique can be easily adapted to other sort orders, for instance, to ignore capitalization or special characters. For more complicated sorting, we use the same idea, but with more complicated data structures or more complicated implementations of the sort.Interface methods.
Example: sorting a playlist *¶
The following example is a music playlist, displayed as a table. Each track is a single row, and each column is an attribute of that track. The playlist can be sorted by any attribute in the columns.
The variable tracks below contains a playlist. Each element is indirect, a pointer to a Track. Although the code below would work if we stored the Tracks directly, the sort function will swap many pairs of elements, so it will run faster if each element is a pointer, which is a single machine word, instead of an entire Track.
type Track struct { Title string Artist string Album string Year int Length time.Duration } var tracks = []*Track{ {"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")}, {"Go", "Moby", "Moby", 1992, length("3m37s")}, {"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")}, {"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")}, } func length(s string) time.Duration { d, err := time.ParseDuration(s) if err != nil { panic(s) } return d }
The printTracks function prints the playlist as a table. It uses the text/tabwriter package to produce a table whose columns are neatly aligned and padded.
*tabwriter.Writersatisfiesio.Writer.- It collects each piece of data written to it.
- Its
Flushmethod formats the entire table and writes it toos.Stdout.
func printTracks(tracks []*Track) { const format = "%v\t%v\t%v\t%v\t%v\t\n" tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0) fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length") fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------") for _, t := range tracks { fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length) } tw.Flush() // calculate column widths and print table }
To sort the playlist by the Artist field, we define a new slice type with the necessary Len, Less, and Swap methods, similar to the StringSlice example.
type byArtist []*Track func (x byArtist) Len() int { return len(x) } func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist } func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
To call the generic sort routine, we must first convert tracks to the new type, byArtist, that defines the order:
sort.Sort(byArtist(tracks))
After sorting the slice by artist, the output from printTracks is:
Title Artist Album Year Length ----- ------ ----- ---- ------ Go Ahead Alicia Keys As I Am 2007 4m36s Go Delilah From the Roots Up 2012 3m38s Ready 2 Go Martin Solveig Smash 2011 4m24s Go Moby Moby 1992 3m37s
To sort by artist in reverse order, we needn't define a new type byReverseArtist with an inverted Less method. The sort package provides a Reverse function that transforms any sort order to its inverse.
sort.Sort(sort.Reverse(byArtist(tracks)))
The sort.Reverse function uses composition (Section 6.3), which is an important idea. The sort package defines an unexported type reverse, which is a struct that embeds a sort.Interface. The Less method for reverse calls the Less method of the embedded sort.Interface value, but with the indices flipped, reversing the order of the sort results.
package sort type reverse struct{ Interface } // that is, sort.Interface func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) } func Reverse(data Interface) Interface { return reverse{data} }
Len and Swap, the other two methods of reverse, are implicitly provided by the original sort.Interface value because it is an embedded field. The exported function Reverse returns an instance of the reverse type that contains the original sort.Interface value.
To sort by a different column, we must define a new type, such as byYear:
type byYear []*Track func (x byYear) Len() int { return len(x) } func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year } func (x byYear) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
Similarly, sort tracks by year using sort.Sort(byYear(tracks)).
For every slice element type and every ordering function we need, we declare a new implementation of sort.Interface; the Len and Swap methods have identical definitions for all slice types.
In the following example, the concrete type customSort combines a slice with a function, so we can define a new sort order by writing only the comparison function. Incidentally, the concrete types that implement sort.Interface are not always slices; customSort is a struct type.
type customSort struct { t []*Track less func(x, y *Track) bool } func (x customSort) Len() int { return len(x.t) } func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) } func (x customSort) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] }
The following code defines a multi-tier ordering function whose primary sort key is the Title, secondary key is the Year, and tertiary key is the running time, Length. This is the call to Sort using an anonymous ordering function:
sort.Sort(customSort{tracks, func(x, y *Track) bool { if x.Title != y.Title { return x.Title < y.Title } if x.Year != y.Year { return x.Year < y.Year } if x.Length != y.Length { return x.Length < y.Length } return false }})
[p190]
Although sorting a sequence of length n requires O(n log n) comparison operations, testing whether a sequence is already sorted requires at most n−1 comparisons. The IsSorted function from the sort package checks this for us. Like sort.Sort, it abstracts both the sequence and its ordering function using sort.Interface, but it never calls the Swap method.
The following code demonstrates the IntsAreSorted and Ints functions and the IntSlice type from the sort package:
values := []int{3, 1, 4, 1} fmt.Println(sort.IntsAreSorted(values)) // "false" sort.Ints(values) fmt.Println(values) // "[1 1 3 4]" fmt.Println(sort.IntsAreSorted(values)) // "true" sort.Sort(sort.Reverse(sort.IntSlice(values))) fmt.Println(values) // "[4 3 1 1]" fmt.Println(sort.IntsAreSorted(values)) // "false"
For convenience, the sort package provides versions of its functions and types specialized for []int, []string, and []float64 using their natural orderings. For other types, such as []int64 or []uint, we need to write types on our own, though the path is short.
The http.Handler Interface¶
Chapter 1 discusses how to use the net/http package to implement web clients (Section 1.5) and servers (Section 1.7). This section focuses on server API, whose foundation is the http.Handler interface:
package http type Handler interface { ServeHTTP(w ResponseWriter, r *Request) } func ListenAndServe(address string, h Handler) error
The ListenAndServe function requires two arguments:
- A server address, such as "localhost:8000"
- An instance of the
Handlerinterface, to which all requests should be dispatched.
This function runs forever, or until the server fails or fails to start; when it fails, it always returns an non-nil error.
The program of the following example shows the simplest implementation of an e-commerce site with a database mapping the items for sale to their prices in dollars. It models the inventory as a map type, database, to which we've attached a ServeHTTP method so that it satisfies the http.Handler interface. The handler ranges over the map and prints the items.
func main() { db := database{"shoes": 50, "socks": 5} log.Fatal(http.ListenAndServe("localhost:8000", db)) } type dollars float32 func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) } type database map[string]dollars func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } }
If we start the server:
$ go build gopl.io/ch7/http1 $ ./http1 &
Then connect to it with the fetch program from Section 1.5 (or a web browser if you prefer), we get the following output:
$ go build gopl.io/ch1/fetch $ ./fetch http://localhost:8000 shoes: $50.00 socks: $5.00
The following program shows a more realistic server that defines multiple different URLs, each triggering a different behavior. It names the existing one /list and adds another one called /price that reports the price of a single item, specified as a request parameter like /price?item=socks.
func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/list": for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } case "/price": item := req.URL.Query().Get("item") price, ok := db[item] if !ok { w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such item: %q\n", item) return } fmt.Fprintf(w, "%s\n", price) default: w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such page: %s\n", req.URL) } }
The handler above decides what logic to execute based on the path component of the URL, req.URL.Path. If the handler doesn't recognize the path, it reports an HTTP error to the client by calling w.WriteHeader(http.StatusNotFound); this must be done before writing any text to w. http.ResponseWriter is another interface, which augments io.Writer with methods for sending HTTP response headers. Equivalently, we could use the http.Error utility function:
msg := fmt.Sprintf("no such page: %s\n", req.URL) http.Error(w, msg, http.StatusNotFound) // 404
The case for /price calls the URL's Query method to parse the HTTP request parameters as a map, or more precisely, a multimap of type url.Values (Section 6.2.1) from the net/url package. It then finds the first item parameter and looks up its price. If the item wasn't found, it reports an error.
The following is an example session with the new server:
$ go build gopl.io/ch7/http2 $ go build gopl.io/ch1/fetch $ ./http2 & $ ./fetch http://localhost:8000/list shoes: $50.00 socks: $5.00 $ ./fetch http://localhost:8000/price?item=socks $5.00 $ ./fetch http://localhost:8000/price?item=shoes $50.00 $ ./fetch http://localhost:8000/price?item=hat no such item: "hat" $ ./fetch http://localhost:8000/help no such page: /help
In a realistic application:
- It's convenient to define the logic for each case in a separate function or method.
- Related URLs may need similar logic. For instance, several image files may have URLs of the form
/images/*.png.
For these reasons, net/http provides ServeMux, a request multiplexer, to simplify the association between URLs and handlers. A ServeMux aggregates a collection of http.Handlers into a single http.Handler. We see that different types satisfying the same interface are substitutable: the web server can dispatch requests to any http.Handler, regardless of which concrete type is behind it.
For a more complex application, several ServeMuxes may be composed to handle more intricate dispatching requirements. [p193]
The program below does the following:
- It creates a
ServeMuxand use it to associate the URLs with the corresponding handlers for the/listand/priceoperations, which have been split into separate methods. - It then uses the
ServeMuxas the main handler in the call toListenAndServe.
func main() { db := database{"shoes": 50, "socks": 5} mux := http.NewServeMux() mux.Handle("/list", http.HandlerFunc(db.list)) mux.Handle("/price", http.HandlerFunc(db.price)) log.Fatal(http.ListenAndServe("localhost:8000", mux)) } type database map[string]dollars func (db database) list(w http.ResponseWriter, req *http.Request) { for item, price := range db { fmt.Fprintf(w, "%s: %s\n", item, price) } } func (db database) price(w http.ResponseWriter, req *http.Request) { item := req.URL.Query().Get("item") price, ok := db[item] if !ok { w.WriteHeader(http.StatusNotFound) // 404 fmt.Fprintf(w, "no such item: %q\n", item) return } fmt.Fprintf(w, "%s\n", price) }
The two calls to mux.Handle register the handlers. In the first one, db.list is a method value (Section 6.4), which is a value of the following type:
func(w http.ResponseWriter, req *http.Request)
When db.list is called, it invokes the database.list method with the receiver value db. So db.list is a function that implements handler-like behavior, but since it has no methods, it doesn't satisfy the http.Handler interface and can't be passed directly to mux.Handle. The expression http.HandlerFunc(db.list) is a conversion, not a function call, since http.HandlerFunc is a type. It has the following definition:
package http type HandlerFunc func(w ResponseWriter, r *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
As a demonstration of some unusual features of Go's interface mechanism, HandlerFunc is a function type that has methods and satisfies an interface, http.Handler. The behavior of its ServeHTTP method is to call the underlying function. HandlerFunc is thus an adapter that lets a function value satisfy an interface, where the function and the interface's sole method have the same signature. In effect, this trick enables a single type such as database to satisfy the http.Handler interface in several different ways: once through its list method, once through its price method, and so on.
Because registering a handler this way is so common, ServeMux has a convenience method HandleFunc simplify the handler registration code:
mux.HandleFunc("/list", db.list)
mux.HandleFunc("/price", db.price)
We could also construct a program in which there are two different web servers, listening on different ports, defining different URLs, and dispatching to different handlers. This can be done by constructing another ServeMux and make another call to ListenAndServe perhaps concurrently. [p195]
For convenience, net/http provides a global ServeMux instance called DefaultServeMux and package-level functions called http.Handle and http.HandleFunc. To use DefaultServeMux as the server's main handler, we needn't pass it to ListenAndServe; nil will do.
The server's main function can then be simplified to:
func main() { db := database{"shoes": 50, "socks": 5} http.HandleFunc("/list", db.list) http.HandleFunc("/price", db.price) log.Fatal(http.ListenAndServe("localhost:8000", nil)) }
As we mentioned in Section 1.7, the web server invokes each handler in a new goroutine, so handlers must take precautions such as locking when accessing variables that other goroutines, including other requests to the same handler, may be accessing. Concurrency is discussed in the next two chapters.
The error Interface¶
The predeclared error type is just an interface type with a single method that returns an error message:
type error interface { Error() string }
The simplest way to create an error is by calling errors.New, which returns a new error for a given error message. The entire errors package is only four lines long:
package errors func New(text string) error { return &errorString{text} } type errorString struct { text string } func (e *errorString) Error() string { return e.text }
The underlying type of errorString is a struct, not a string, to protect its representation from inadvertent (or premeditated) updates. The pointer type *errorString is used to satisfy the error interface rather than errorString alone so that every call to New allocates a distinct error instance that is not equal to each other. We would not want a distinguished error such as io.EOF to compare equal to one that may happen to have the same message.
fmt.Println(errors.New("EOF") == errors.New("EOF")) // "false"
Calls to errors.New are infrequent because there's a convenient wrapper function, fmt.Errorf that does string formatting. It is used in Chapter 5.
package fmt import "errors" func Errorf(format string, args ...interface{}) error { return errors.New(Sprintf(format, args...)) }
Although *errorString may be the simplest type of error but not the only one. For example, the syscall package provides Go's low-level system call API. On many platforms, it defines a numeric type Errno that satisfies error, and on Unix platforms, Errno's Error method does a lookup in a table of strings, as shown below:
package syscall type Errno uintptr // operating system error code var errors = [...]string{ 1: "operation not permitted", // EPERM 2: "no such file or directory", // ENOENT 3: "no such process", // ESRCH // ... } func (e Errno) Error() string { if 0 <= int(e) && int(e) < len(errors) { return errors[e] } return fmt.Sprintf("errno %d", e) }
The following statement creates an interface value holding the Errno value 2, signifying the POSIX ENOENT condition:
var err error = syscall.Errno(2) fmt.Println(err.Error()) // "no such file or directory" fmt.Println(err) // "no such file or directory"
The value of err is shown graphically the following figure:
Errno is an efficient representation of system call errors drawn from a finite set, and it satisfies the standard error interface. Other types that satisfy this interface will be discussed in Section 7.11.
Example: Expression Evaluator¶
[skipped]
Type Assertions¶
A type assertion is an operation applied to an interface value. Syntactically, it looks like x.(T), where x is an expression of an interface type and T is a type, called the "asserted" type. A type assertion checks that the dynamic type of its operand matches the asserted type, which has two possibilities as discussed below.
If the asserted type T is a concrete type, then the type assertion checks whether x's dynamic type is identical to T. If this check succeeds, the result of the type assertion is x's dynamic value, whose type is of course T. In other words, a type assertion to a concrete type extracts the concrete value from its operand. If the check fails, then the operation panics. For example:
var w io.Writer w = os.Stdout f := w.(*os.File) // success: f == os.Stdout c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
If the asserted type T is an interface type, then the type assertion checks whether x's dynamic type satisfies T. If this check succeeds, the dynamic value is not extracted; the result is still an interface value with the same type and value components, but the result has the interface type T. In other words, a type assertion to an interface type changes the type of the expression, making a different (and usually larger) set of methods accessible, but it preserves the dynamic type and value components inside the interface value.
After the first type assertion below, both w and rw hold os.Stdout so each has a dynamic type of *os.File, but w, an io.Writer, exposes only the file's Write method, whereas rw also exposes its Read method.
var w io.Writer w = os.Stdout rw := w.(io.ReadWriter) // success: *os.File has both Read and Write w = new(ByteCounter) rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method
If the operand is a nil interface value, the type assertion always fails. A type assertion to a less restrictive interface type (one with fewer methods) is rarely needed, as it behaves just like an assignment, except in the nil case.
w = rw // io.ReadWriter is assignable to io.Writer w = rw.(io.Writer) // fails only if rw == nil
When we're not sure of the dynamic type of an interface value, we can test whether it is some particular type. If the type assertion appears in an assignment in which two results are expected, such as the following declarations, the operation does not panic on failure but instead returns an additional second result, a boolean indicating success:
var w io.Writer = os.Stdout f, ok := w.(*os.File) // success: ok, f == os.Stdout b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
The second result is conventionally assigned to a variable named ok. If the operation failed, ok is false, and the first result is equal to the zero value of the asserted type, which in this example is a nil *bytes.Buffer.
The ok result is often immediately used to decide what to do next, as written in an if statement of the following form:
if f, ok := w.(*os.File); ok { // ...use f... }
When the operand of a type assertion is a variable, rather than invent another name for the new local variable, you'll sometimes see the original name reused, shadowing the original, like this:
if w, ok := w.(*os.File); ok { // ...use w... }
Discriminating Errors with Type Assertions¶
Consider the set of errors returned by file operations in the os package. I/O can fail for any number of reasons, but three kinds of failure often must be handled differently:
- file already exists (for create operations)
- file not found (for read operations)
- permission denied
The os package provides three helper functions to classify the failure indicated by a given error value:
package os func IsExist(err error) bool func IsNotExist(err error) bool func IsPermission(err error) bool
A naïve implementation might check that the error message contains a certain substring:
func IsNotExist(err error) bool { // NOTE: not robust! return strings.Contains(err.Error(), "file does not exist") }
Because the logic for handling I/O errors can vary from one platform to another, this approach is not robust and the same failure may be reported with a variety of different error messages.
A more reliable approach is to represent structured error values using a dedicated type. The os package defines these types:
PathError, which describes failures involving an operation on a file path, likeOpenorDelete- A variant
LinkError, which describes failures of operations involving two file paths, likeSymlinkandRename
The following is os.PathError:
package os // PathError records an error and the operation and file path that caused it. type PathError struct { Op string Path string Err error } func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
Although PathError's Error method forms a message by simply concatenating the fields, PathError's structure preserves the underlying components of the error. Clients can use a type assertion to detect the specific type of the error; the specific type provides more detail than a simple string.
_, err := os.Open("/no/such/file") fmt.Println(err) // "open /no/such/file: No such file or directory" fmt.Printf("%#v\n", err) // Output: // &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2}
That is how the three helper functions work. For example, IsNotExist, shown below, reports whether an error is equal to syscall.ENOENT (Section 7.8) or to the distinguished error os.ErrNotExist (see io.EOF in Section 5.4.2), or is a *PathError whose underlying error is one of those two.
import ( "errors" "syscall" ) var ErrNotExist = errors.New("file does not exist") // IsNotExist returns a boolean indicating whether the error is known to // report that a file or directory does not exist. It is satisfied by // ErrNotExist as well as some syscall errors. func IsNotExist(err error) bool { if pe, ok := err.(*PathError); ok { err = pe.Err } return err == syscall.ENOENT || err == ErrNotExist }
This is how it is used:
_, err := os.Open("/no/such/file") fmt.Println(os.IsNotExist(err)) // "true"
PathError's structure is lost if the error message is combined into a larger string, for instance by a call to fmt.Errorf. Error discrimination must usually be done immediately after the failing operation, before an error is propagated to the caller.
Querying Behaviors with Interface Type Assertions¶
The code below is similar to the part of the net/http web server responsible for writing HTTP header fields. The io.Writer w represents the HTTP response; the bytes written to it are sent to the web browser.
func writeHeader(w io.Writer, contentType string) error { if _, err := w.Write([]byte("Content-Type: ")); err != nil { return err } if _, err := w.Write([]byte(contentType)); err != nil { return err } // ... }
Because the Write method requires a byte slice, a []byte(...) conversion from a string is required. This conversion allocates memory and makes a copy, but the copy is discarded almost immediately after. Can we avoid allocating memory here?
The io.Writer interface tells us only one fact about the concrete type that w holds: that bytes may be written to it. If we look behind the curtains of the net/http package, we see that the dynamic type that w holds in this program also has a WriteString method that allows strings to be efficiently written to it, avoiding the need to allocate a temporary copy. A number of important types that satisfy io.Writer also have a WriteString method, including *bytes.Buffer, *os.File and *bufio.Writer.
We cannot assume that an arbitrary io.Writer w also has the WriteString method. But we can define a new interface that has just this method and use a type assertion to test whether the dynamic type of w satisfies this new interface.
// writeString writes s to w. // If w has a WriteString method, it is invoked instead of w.Write. func writeString(w io.Writer, s string) (n int, err error) { type stringWriter interface { WriteString(string) (n int, err error) } if sw, ok := w.(stringWriter); ok { return sw.WriteString(s) // avoid a copy } return w.Write([]byte(s)) // allocate temporary copy } func writeHeader(w io.Writer, contentType string) error { if _, err := writeString(w, "Content-Type: "); err != nil { return err } if _, err := writeString(w, contentType); err != nil { return err } // ... }
The standard library provides it as io.WriteString; it is the recommended way to write a string to an io.Writer.
From this example, we realize that:
- There is no standard interface that defines the
WriteStringmethod and specifies its required behavior. - Whether or not a concrete type satisfies the
stringWriterinterface is determined only by its methods, not by any declared relationship between it and the interface type.
What this means is that the technique above relies on the assumption that if a type satisfies the interface below, then WriteString(s) must have the same effect as Write([]byte(s)).
interface { io.Writer WriteString(s string) (n int, err error) }
Although io.WriteString documents its assumption, few functions that call it are likely to document that they make the same assumption. Defining a method of a particular type is taken as an implicit assent for a certain behavioral contract. This lack of explicit intention seems unsettling, but it is rarely a problem in practice. With the exception of the empty interface interface{}, interface types are seldom satisfied by unintended coincidence.
The writeString function above uses a type assertion to see whether a value of a general interface type also satisfies a more specific interface type, and if so, it uses the behaviors of the specific interface. This technique can be put to good use whether or not the queried interface is standard like io.ReadWriter or user-defined like stringWriter.
It's also how fmt.Fprintf distinguishes values that satisfy error or fmt.Stringer from all other values. Within fmt.Fprintf, there is a step that converts a single operand to a string, something like this:
package fmt func formatOneValue(x interface{}) string { if err, ok := x.(error); ok { return err.Error() } if str, ok := x.(Stringer); ok { return str.String() } // ...all other types... }
If x satisfies either of the two interfaces, that determines the formatting of the value. If not, the default case handles all other types more or less uniformly using reflection, which is discussed in Chapter 12.
Again, this makes the assumption that any type with a String method satisfies the behavioral contract of fmt.Stringer, which is to return a string suitable for printing.
Type Switches¶
Interfaces are used in two distinct styles.
- The first style is exemplified by interfaces such as
io.Reader,io.Writer,fmt.Stringer,sort.Interface,http.Handler, anderror.- In this style, an interface's methods express the similarities of the concrete types that satisfy the interface but hide the representation details and intrinsic operations of those concrete types.
- The emphasis is on the methods, not on the concrete types.
- The second style exploits the ability of an interface value to hold values of a variety of concrete types and considers the interface to be the union of those types.
- Type assertions are used to discriminate among these types dynamically and treat each case differently.
- The emphasis is on the concrete types that satisfy the interface, not on the interface's methods, and there is no hiding of information.
- The interfaces used this way are described as discriminated unions.
In object-oriented programming, these two styles may be recognized as subtype polymorphism and ad hoc polymorphism.
Go's API for querying an SQL database separates the fixed part of a query from the variable parts. In the following example:
import "database/sql" func listTracks(db sql.DB, artist string, minYear, maxYear int) { result, err := db.Exec( "SELECT * FROM tracks WHERE artist = ? AND ? <= year AND year <= ?", artist, minYear, maxYear) // ... }
The Exec method replaces each '?' in the query string with an SQL literal denoting the corresponding argument value. Constructing queries this way helps avoid SQL injection attacks, in which an adversary takes control of the query by exploiting improper quotation of input data.
Within Exec, we might find a function like the one below, which converts each argument value to its literal SQL notation.
func sqlQuote(x interface{}) string { if x == nil { return "NULL" } else if _, ok := x.(int); ok { return fmt.Sprintf("%d", x) } else if _, ok := x.(uint); ok { return fmt.Sprintf("%d", x) } else if b, ok := x.(bool); ok { if b { return "TRUE" } return "FALSE" } else if s, ok := x.(string); ok { return sqlQuoteString(s) // (not shown) } else { panic(fmt.Sprintf("unexpected type %T: %v", x, x)) } }
A switch statement simplifies an if-else chain that performs a series of value equality tests. An analogous type switch statement simplifies an if-else chain of type assertions.
In its simplest form:
- A type switch looks like an ordinary
switchstatement in which the operand isx.(type)(that's literally the keywordtype) and each case has one or more types. - A type switch enables a multi-way branch based on the interface value's dynamic type. The
nilcase matches ifx == nil, and thedefaultcase matches if no other case does.
A type switch for sqlQuote would have these cases:
switch x.(type) { case nil: // ... case int, uint: // ... case bool: // ... case string: // ... default: // ... }
As with an ordinary switch statement (Section 1.8):
- Cases are considered in order. When a match is found, the case's body is executed.
- Case order becomes significant when one or more case types are interfaces, since then there is a possibility of two cases matching.
- The position of the
defaultcase relative to the others is immaterial. - No
fallthroughis allowed.
In the original function, the logic for the bool and string cases needs access to the value extracted by the type assertion. The type switch statement has an extended form that binds the extracted value to a new variable within each case:
switch x := x.(type) { /* ... */ }
The new variables is called x, since reuse of variable names is common, as with type assertions. Like a switch statement, a type switch implicitly creates a lexical block, so the declaration of the new variable called x does not conflict with a variable x in an outer block. Each case also implicitly creates a separate lexical block.
sqlQuote can be rewritten to use the extended form of type switch:
func sqlQuote(x interface{}) string { switch x := x.(type) { case nil: return "NULL" case int, uint: return fmt.Sprintf("%d", x) // x has type interface{} here. case bool: if x { return "TRUE" } return "FALSE" case string: return sqlQuoteString(x) // (not shown) default: panic(fmt.Sprintf("unexpected type %T: %v", x, x)) } }
In the above code:
- Within the block of each single-type case, the variable
xhas the same type as the case.- For instance,
xhas typeboolwithin theboolcase andstringwithin thestringcase.
- For instance,
- In all other cases,
xhas the (interface) type of theswitchoperand, which isinterface{}. - When the same action is required for multiple cases, like
intanduint, the type switch makes it easy to combine them.
Although sqlQuote accepts an argument of any type, the function runs to completion only if the argument's type matches one of the cases in the type switch; otherwise it panics with an "unexpected type" message. Although the type of x is interface{}, we consider it a discriminated union of int, uint, bool, string, and nil.
Example: Token-Based XML Decoding¶
Section 4.5 showed how to decode JSON documents into Go data structures with the Marshal and Unmarshal functions from the encoding/json package. The encoding/xml package provides a similar API.
This approach is convenient when we want to construct a representation of the document tree, but that's unnecessary for many programs. The encoding/xml package also provides a lower-level token-based API for decoding XML. In the token-based style, the parser consumes the input and produces a stream of tokens, primarily of four kinds:
StartElementEndElementCharDataComment
Each of them is a concrete type in the encoding/xml package. Each call to (*xml.Decoder).Token returns a token. The relevant parts of the API are shown here:
package xml type Name struct { Local string // e.g., "Title" or "id" } type Attr struct { // e.g., name="value" Name Name Value string } // A Token includes StartElement, EndElement, CharData, // and Comment, plus a few esoteric types (not shown). type Token interface{} type StartElement struct { // e.g., <name> Name Name Attr []Attr } type EndElement struct { Name Name } // e.g., </name> type CharData []byte // e.g., <p>CharData</p> type Comment []byte // e.g., <!-- Comment --> type Decoder struct{ /* ... */ } func NewDecoder(io.Reader) *Decoder func (*Decoder) Token() (Token, error) // returns next Token in sequence
The Token interface, which has no methods, is also an example of a discriminated union.
- The purpose of a traditional interface like
io.Readeris to hide details of the concrete types that satisfy it so that new implementations can be created; each concrete type is treated uniformly. - By contrast, the set of concrete types that satisfy a discriminated union is fixed by the design and exposed, not hidden. Discriminated union types have few methods; functions that operate on them are expressed as a set of cases using a type switch, with different logic in each case.
The xmlselect program below extracts and prints the text found beneath certain elements in an XML document tree. Using the API above, it can do its job in a single pass over the input without ever materializing the tree.
// Xmlselect prints the text of selected elements of an XML document. package main import ( "encoding/xml" "fmt" "io" "os" "strings" ) func main() { dec := xml.NewDecoder(os.Stdin) var stack []string // stack of element names for { tok, err := dec.Token() if err == io.EOF { break } else if err != nil { fmt.Fprintf(os.Stderr, "xmlselect: %v\n", err) os.Exit(1) } switch tok := tok.(type) { case xml.StartElement: stack = append(stack, tok.Name.Local) // push case xml.EndElement: stack = stack[:len(stack)-1] // pop case xml.CharData: if containsAll(stack, os.Args[1:]) { fmt.Printf("%s: %s\n", strings.Join(stack, " "), tok) } } } } // containsAll reports whether x contains the elements of y, in order. func containsAll(x, y []string) bool { for len(y) <= len(x) { if len(y) == 0 { return true } if x[0] == y[0] { y = y[1:] } x = x[1:] } return false }
- Each time the loop in main encounters a
StartElement, it pushes the elements' name onto a stack, and for eachEndElementit pops the name from the stack. - The API guarantees that the sequence of
StartElementandEndElementtokens will be properly matched, even in illformed documents. Commentsare ignored.- When
xmlselectencounters aCharData, it prints the text only if the stack contains all the elements named by the command-line arguments, in order.
The command below prints the text of any h2 elements appearing beneath two levels of div elements.
$ go build gopl.io/ch1/fetch
$ ./fetch http://www.w3.org/TR/2006/REC-xml11-20060816 |
./xmlselect div div h2
html body div div h2: 1 Introduction
html body div div h2: 2 Documents
html body div div h2: 3 Logical Structures
html body div div h2: 4 Physical Structures
html body div div h2: 5 Conformance
html body div div h2: 6 Notation
html body div div h2: A References
html body div div h2: B Definitions for Character Normalization
...
A Few Words of Advice¶
When designing a new package, novice Go programmers often start by creating a set of interfaces and only later define the concrete types that satisfy them. This approach is not recommended, because:
- It results in many interfaces, each of which has only a single implementation. These interfaces are unnecessary abstractions.
- These interfaces also have a run-time cost.
You can restrict which methods of a type or fields of a struct are visible outside a package using the export mechanism (Section 6.6). Interfaces are only needed when there are two or more concrete types that must be dealt with in a uniform way.
An exception to this rule is: when an interface is satisfied by a single concrete type but that type cannot live in the same package as the interface because of its dependencies. In that case, an interface is a good way to decouple two packages.
Because interfaces are used only when they are satisfied by two or more types, they necessarily abstract away from the details of any particular implementation. The result is smaller interfaces with fewer, simpler methods, often just one as with io.Writer or fmt.Stringer. Small interfaces are easier to satisfy when new types come along. A good rule of thumb for interface design is ask only for what you need.
With methods and interfaces, Go has great support for the object-oriented style of programming, but this does not mean you need to use it exclusively. Not everything need be an object; standalone functions have their place, as do unencapsulated data types.
Doubts and Solution¶
Verbatim¶
p210 on assumption of WriteString¶
Although
io.WriteStringdocuments its assumption, few functions that call it are likely to document that they make the same assumption. Defining a method of a particular type is taken as an implicit assent for a certain behavioral contract.
Question: What does it mean?





