Key Standard Library Interfaces
Go's standard library provides powerful, general-purpose interfaces. Understanding and implementing these interfaces lets you write code that integrates naturally with the entire standard library.
io.Reader and io.Writerβ
The two most important interfaces in Go. They abstract I/O so that files, network connections, memory buffers, and more can all be handled the same way.
package main
import (
"bytes"
"fmt"
"io"
"strings"
)
// io.Reader: interface for reading data
// type Reader interface {
// Read(p []byte) (n int, err error)
// }
// io.Writer: interface for writing data
// type Writer interface {
// Write(p []byte) (n int, err error)
// }
// Custom Reader implementation
type CountingReader struct {
r io.Reader
count int
}
func (cr *CountingReader) Read(p []byte) (int, error) {
n, err := cr.r.Read(p)
cr.count += n
return n, err
}
func (cr *CountingReader) BytesRead() int {
return cr.count
}
// Custom Writer implementation
type PrefixWriter struct {
w io.Writer
prefix string
}
func (pw *PrefixWriter) Write(p []byte) (int, error) {
// Add prefix to each line
lines := strings.Split(string(p), "\n")
for i, line := range lines {
if i < len(lines)-1 {
fmt.Fprintf(pw.w, "%s%s\n", pw.prefix, line)
}
}
return len(p), nil
}
func main() {
// io.Copy: copies from Reader to Writer
src := strings.NewReader("Hello, Go interfaces!\nSecond line\nThird line")
cr := &CountingReader{r: src}
var buf bytes.Buffer
pw := &PrefixWriter{w: &buf, prefix: ">>> "}
io.Copy(pw, cr)
fmt.Print(buf.String())
fmt.Printf("bytes read: %d\n", cr.BytesRead())
// Standard library Readers
readers := []io.Reader{
strings.NewReader("from string"),
bytes.NewReader([]byte("from bytes")),
}
multi := io.MultiReader(readers...)
data, _ := io.ReadAll(multi)
fmt.Println("MultiReader:", string(data))
}
fmt.Stringerβ
Implementing fmt.Stringer provides a custom string representation used by fmt.Println and others.
package main
import (
"fmt"
"strings"
)
// fmt.Stringer interface
// type Stringer interface {
// String() string
// }
type Direction int
const (
North Direction = iota
South
East
West
)
func (d Direction) String() string {
switch d {
case North:
return "North"
case South:
return "South"
case East:
return "East"
case West:
return "West"
default:
return fmt.Sprintf("Direction(%d)", int(d))
}
}
type Point struct {
X, Y float64
}
func (p Point) String() string {
return fmt.Sprintf("(%.1f, %.1f)", p.X, p.Y)
}
type Person struct {
Name string
Age int
Tags []string
}
func (p Person) String() string {
tags := ""
if len(p.Tags) > 0 {
tags = fmt.Sprintf(" [%s]", strings.Join(p.Tags, ", "))
}
return fmt.Sprintf("%s(%d)%s", p.Name, p.Age, tags)
}
// GoString: used with %#v format (fmt.GoStringer interface)
func (p Person) GoString() string {
return fmt.Sprintf("Person{Name:%q, Age:%d, Tags:%v}", p.Name, p.Age, p.Tags)
}
func main() {
dir := North
fmt.Println(dir) // North
fmt.Printf("direction: %v\n", dir) // direction: North
fmt.Printf("value: %d\n", dir) // value: 0
pt := Point{3.14, 2.72}
fmt.Println(pt) // (3.1, 2.7)
p := Person{
Name: "John Doe",
Age: 30,
Tags: []string{"Go", "Developer"},
}
fmt.Println(p) // John Doe(30) [Go, Developer]
fmt.Printf("%#v\n", p) // Person{Name:"John Doe", Age:30, Tags:[Go Developer]}
// Automatically applied in slices
directions := []Direction{North, East, South, West}
fmt.Println(directions) // [North East South West]
}
The error Interfaceβ
In Go, an error is simply an interface with a single Error() string method.
package main
import (
"errors"
"fmt"
)
// error interface
// type error interface {
// Error() string
// }
// Hierarchical error type
type AppError struct {
Code int
Message string
Err error // wrapped cause error
}
func (e *AppError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)
}
return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}
func (e *AppError) Unwrap() error {
return e.Err
}
// Domain-specific error
type DBError struct {
Query string
Err error
}
func (e *DBError) Error() string {
return fmt.Sprintf("DB error (query=%s): %v", e.Query, e.Err)
}
func (e *DBError) Unwrap() error { return e.Err }
var (
ErrNotFound = errors.New("resource not found")
ErrUnauthorized = errors.New("unauthorized")
)
func queryDB(id int) error {
if id == 0 {
return &DBError{
Query: "SELECT * FROM users WHERE id=0",
Err: errors.New("invalid ID"),
}
}
if id > 1000 {
return &AppError{
Code: 404,
Message: "user not found",
Err: fmt.Errorf("DB lookup failed: %w", ErrNotFound),
}
}
return nil
}
func main() {
testIDs := []int{0, 42, 9999}
for _, id := range testIDs {
err := queryDB(id)
if err == nil {
fmt.Printf("ID=%d: success\n", id)
continue
}
fmt.Printf("ID=%d: %v\n", id, err)
// Walk the error chain
var dbErr *DBError
if errors.As(err, &dbErr) {
fmt.Printf(" β DB query: %s\n", dbErr.Query)
}
var appErr *AppError
if errors.As(err, &appErr) {
fmt.Printf(" β app error code: %d\n", appErr.Code)
}
if errors.Is(err, ErrNotFound) {
fmt.Println(" β 404 handling needed")
}
}
}
sort.Interfaceβ
Used to customize sorting behavior.
package main
import (
"fmt"
"sort"
)
// sort.Interface
// type Interface interface {
// Len() int
// Less(i, j int) bool
// Swap(i, j int)
// }
type Student struct {
Name string
Grade int
Score float64
}
// Sort by score descending
type ByScore []Student
func (s ByScore) Len() int { return len(s) }
func (s ByScore) Less(i, j int) bool { return s[i].Score > s[j].Score } // descending
func (s ByScore) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// Sort by name ascending
type ByName []Student
func (s ByName) Len() int { return len(s) }
func (s ByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
func (s ByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func printStudents(students []Student) {
for _, s := range students {
fmt.Printf(" %s (grade:%d, score:%.1f)\n", s.Name, s.Grade, s.Score)
}
}
func main() {
students := []Student{
{"Charlie", 3, 87.5},
{"Alice", 1, 95.0},
{"Bob", 2, 87.5},
{"Diana", 1, 91.0},
{"Eve", 3, 78.0},
}
// Sort by score descending
sort.Sort(ByScore(students))
fmt.Println("By score (descending):")
printStudents(students)
// Sort by name ascending
sort.Sort(ByName(students))
fmt.Println("By name (ascending):")
printStudents(students)
// sort.Slice β sort with closure, no interface needed (Go 1.8+)
sort.Slice(students, func(i, j int) bool {
if students[i].Grade != students[j].Grade {
return students[i].Grade < students[j].Grade // grade ascending
}
return students[i].Score > students[j].Score // score descending
})
fmt.Println("By grade asc, score desc:")
printStudents(students)
}
http.Handlerβ
The core interface for web server development.
package main
import (
"fmt"
"net/http"
"strings"
)
// http.Handler interface
// type Handler interface {
// ServeHTTP(ResponseWriter, *Request)
// }
// Custom handler
type GreetHandler struct {
greeting string
}
func (h GreetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
fmt.Fprintf(w, "%s, %s!\n", h.greeting, name)
}
// Middleware pattern (handler wrapping a handler)
type LoggingHandler struct {
handler http.Handler
}
func (lh LoggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Printf("[LOG] %s %s\n", r.Method, r.URL.Path)
lh.handler.ServeHTTP(w, r)
}
type AuthHandler struct {
handler http.Handler
token string
}
func (ah AuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
auth := r.Header.Get("Authorization")
if !strings.HasPrefix(auth, "Bearer "+ah.token) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
ah.handler.ServeHTTP(w, r)
}
// http.HandlerFunc β converts a function to a Handler
func helloFunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "function-based handler")
}
func main() {
// Struct-based handler
greet := GreetHandler{greeting: "Hello"}
logged := LoggingHandler{handler: greet}
// Simulate instead of running actual server
fmt.Println("http.Handler interface implemented")
fmt.Printf("GreetHandler: %T\n", greet)
fmt.Printf("LoggingHandler: %T\n", logged)
fmt.Printf("HandlerFunc: %T\n", http.HandlerFunc(helloFunc))
// To run: http.ListenAndServe(":8080", logged)
}
io.Closer and defer Patternβ
package main
import (
"fmt"
"io"
"strings"
)
// io.Closer
// type Closer interface {
// Close() error
// }
type Resource struct {
name string
closed bool
}
func (r *Resource) Read(p []byte) (int, error) {
if r.closed {
return 0, fmt.Errorf("%s: already closed", r.name)
}
data := []byte("data from " + r.name)
copy(p, data)
return len(data), io.EOF
}
func (r *Resource) Close() error {
if r.closed {
return fmt.Errorf("%s: already closed", r.name)
}
r.closed = true
fmt.Printf("[%s] resource closed\n", r.name)
return nil
}
func openResource(name string) io.ReadCloser {
return &Resource{name: name}
}
func processResource(name string) error {
rc := openResource(name)
defer rc.Close() // guaranteed to close
var sb strings.Builder
buf := make([]byte, 64)
for {
n, err := rc.Read(buf)
if n > 0 {
sb.Write(buf[:n])
}
if err == io.EOF {
break
}
if err != nil {
return err
}
}
fmt.Printf("[%s] read: %s\n", name, sb.String())
return nil
}
func main() {
processResource("DB connection")
processResource("file handle")
}
Key Summary
io.Reader/io.Writer: Go I/O core β abstracts all I/O sources and destinationsfmt.Stringer: implementString() stringfor custom output formattingerror: simple interface with justError() string; chain withUnwrap()sort.Interface: custom sort criteria (or usesort.Slice)http.Handler: foundation of web server middleware chainsio.Closer: usedefer rc.Close()to guarantee resource cleanup