Useful Standard Interfaces
A handful of small interfaces from the standard library connect your types to the rest of Go. Give a type a String method and fmt prints it nicely; an Error method makes it an error; Read or Write methods let it plug into files, network connections, compression and hashing. This lesson shows the interfaces you'll implement most often and what you get for free when you do.
fmt.Stringer
type Stringer interface { String() string }
When a value has a String() string method, fmt's %v and %s (and Println) use it. It's the standard way to give enum-like constants readable names:
package main import "fmt" type Weekday int const ( Sunday Weekday = iota Monday Tuesday ) var dayNames = [...]string{"Sunday", "Monday", "Tuesday"} func (d Weekday) String() string { if d < 0 || int(d) >= len(dayNames) { return fmt.Sprintf("Weekday(%d)", int(d)) } return dayNames[d] } type Money int64 // cents func (m Money) String() string { return fmt.Sprintf("€%d.%02d", m/100, m%100) } func main() { fmt.Println(Monday, Tuesday) fmt.Printf("%v %d %s\n", Sunday, Sunday, Weekday(9)) price := Money(1999) fmt.Println("price:", price) fmt.Printf("%v\n", []Money{250, 1000}) }
Monday Tuesday Sunday 0 Weekday(9) price: €19.99 [€2.50 €10.00]
%d still prints the number. Inside a String method, convert to the underlying type before formatting the value itself — fmt.Sprintf("%v", d) inside d.String() would call String again forever. (For larger enums, the stringer tool from golang.org/x/tools generates these methods.)
error
type error interface { Error() string }
Any type with an Error() string method is an error. Custom error types can carry extra details for the caller to inspect (lesson 45):
package main import "fmt" type ValidationError struct { Field string Reason string } func (e *ValidationError) Error() string { return e.Field + ": " + e.Reason } func validateAge(age int) error { if age < 0 || age > 150 { return &ValidationError{Field: "age", Reason: fmt.Sprintf("%d is out of range", age)} } return nil } func main() { fmt.Println(validateAge(30)) err := validateAge(-4) fmt.Println(err) if ve, ok := err.(*ValidationError); ok { fmt.Println("bad field:", ve.Field) } }
<nil> age: -4 is out of range bad field: age
io.Reader and io.Writer
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) }
These two are the backbone of Go's input and output. Files, network connections, HTTP bodies, buffers, compressors, hashers and standard output all implement one or both, so code written against io.Reader and io.Writer works with all of them. Here one function writes a report to three very different destinations:
package main import ( "crypto/sha256" "fmt" "io" "os" "strings" ) func writeReport(w io.Writer, items []string) error { for i, it := range items { if _, err := fmt.Fprintf(w, "%d. %s\n", i+1, it); err != nil { return err } } return nil } func main() { items := []string{"apples", "bread"} writeReport(os.Stdout, items) // the terminal var sb strings.Builder writeReport(&sb, items) // a string fmt.Printf("%q\n", sb.String()) h := sha256.New() writeReport(h, items) // a hash fmt.Printf("%x\n", h.Sum(nil)[:8]) }
1. apples 2. bread "1. apples\n2. bread\n" 4cd35472f67f33c8
Writing your own Writer
Implementing Write takes a few lines. This writer counts words passing through it and forwards the bytes to another writer — a pattern called a wrapper or decorator:
package main import ( "bytes" "fmt" "io" "os" "strings" ) type WordCounter struct { W io.Writer Words int } func (wc *WordCounter) Write(p []byte) (int, error) { wc.Words += len(bytes.Fields(p)) return wc.W.Write(p) } func main() { wc := &WordCounter{W: os.Stdout} src := strings.NewReader("the quick brown fox\njumps over the lazy dog\n") if _, err := io.Copy(wc, src); err != nil { fmt.Println(err) } fmt.Println("words:", wc.Words) }
the quick brown fox jumps over the lazy dog words: 9
io.Copy moves everything from any reader to any writer. (Counting words chunk by chunk like this would miscount a word split across two chunks; with this short input it all arrives in one Write.) Lesson 58 covers readers and writers in depth.
io.Closer
Close() error releases a resource. Files, network connections and HTTP response bodies implement it, usually combined with reading or writing as io.ReadCloser or io.WriteCloser. Whenever you get something that implements Closer, make sure it's closed — typically with defer x.Close().
Other interfaces worth knowing
| Interface | Method | What it's for |
|---|---|---|
fmt.Stringer | String() string | How a value prints |
error | Error() string | Error values |
io.Reader / io.Writer | Read / Write | Streams of bytes |
io.Closer | Close() error | Releasing resources |
json.Marshaler / Unmarshaler | MarshalJSON / UnmarshalJSON | Custom JSON encoding (lesson 59) |
encoding.TextMarshaler | MarshalText | A value's text form, used by JSON keys, flags, logs |
http.Handler | ServeHTTP(w, r) | Answering web requests (lesson 61) |
sort.Interface | Len, Less, Swap | Older sorting API; prefer slices.SortFunc |
Try it yourself
Give the Temperature type a String method that prints one decimal and the unit, then print a slice of them.
[21.5°C 19.0°C 23.2°C] max today: 23.2°C