iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Interfaces

Go interfaces are implicit — a type satisfies an interface by having the right methods, no implements keyword. The empty interface (any / interface{}) holds any value; specific interfaces enable polymorphism.

Define, satisfy, embed, type assert

EXAMPLE
package main

import (
    "fmt"
    "io"
    "os"
    "strings"
)

// 1) Define an interface
type Stringer interface {
    String() string
}

// 2) Implicit implementation — no `implements` keyword
type User struct{ Name, Email string }

func (u User) String() string {
    return fmt.Sprintf("%s <%s>", u.Name, u.Email)
}

// User satisfies Stringer because it has the right method.

func main() {
    var s Stringer = User{Name: "Ada", Email: "a@x.com"}
    fmt.Println(s.String())
}

// 3) Polymorphism — accept any Stringer
func printAll(list []Stringer) {
    for _, s := range list {
        fmt.Println(s.String())
    }
}

// 4) Standard-library interfaces you already know
// io.Reader  — Read(p []byte) (n int, err error)
// io.Writer  — Write(p []byte) (n int, err error)
// io.Closer  — Close() error
// fmt.Stringer — String() string
// error      — Error() string
// sort.Interface — Len, Less, Swap

func copyTo(dst io.Writer, src io.Reader) error {
    _, err := io.Copy(dst, src)
    return err
}

// Pass any of: os.Stdout, *os.File, *bytes.Buffer, http.ResponseWriter, ...
copyTo(os.Stdout, strings.NewReader("hello\n"))

// 5) Interface composition — embed smaller interfaces
type ReadWriter interface {
    io.Reader
    io.Writer
}

type ReadWriteCloser interface {
    io.Reader
    io.Writer
    io.Closer
}

// 6) Type assertion — extract the concrete type
var w io.Writer = os.Stdout
if f, ok := w.(*os.File); ok {
    fmt.Println("have file:", f.Name())
}

// 7) Type switch — branch on concrete type
func describe(v any) string {
    switch x := v.(type) {
    case int:        return fmt.Sprintf("int %d", x)
    case string:     return fmt.Sprintf("string %q", x)
    case fmt.Stringer:return x.String()
    case nil:        return "nil"
    default:         return fmt.Sprintf("%T %v", x, x)
    }
}

// 8) Empty interface — any (Go 1.18+ alias)
var v any = 42
v = "hello"
v = []int{1, 2, 3}

// 9) Nil interfaces — a subtle gotcha
var e error = nil
fmt.Println(e == nil)            // true

var bad *MyError = nil
e = bad                          // interface now has TYPE info but no VALUE
fmt.Println(e == nil)            // FALSE — surprise!

// Fix: return nil error explicitly when there's no error:
func work() error {
    var err *MyError
    // ...
    if err == nil {
        return nil                // not `return err`
    }
    return err
}

// 10) Accept interfaces, return concrete types
// Idiomatic Go: function args should be the smallest interface that works.
func CalcChecksum(r io.Reader) (uint32, error) { /* … */ }
// vs `func CalcChecksum(f *os.File)` — the interface version takes strings.Reader, http body, etc.

// 11) Methods on pointer vs value — affects interface satisfaction
type Counter struct{ n int }

func (c *Counter) Inc()           { c.n++ }            // pointer receiver
func (c Counter)  Value() int     { return c.n }       // value receiver

type Incrementer interface { Inc() }

var i Incrementer = &Counter{}    // OK — *Counter has Inc()
// var i Incrementer = Counter{}   // ERROR — Counter (value) doesn't satisfy

// 12) Generics (Go 1.18+) sometimes replace interfaces
func Map[T, U any](in []T, fn func(T) U) []U {
    out := make([]U, len(in))
    for i, v := range in {
        out[i] = fn(v)
    }
    return out
}

Why it matters

Idiomatic Go: accept the smallest interface that works (io.Reader), return concrete types. Implicit satisfaction means new types just plug in — no inheritance ceremony.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
type Stringer interface {
    String() string
}

func (u User) String() string { return u.Name }
Try it Yourself »

Discussion

Loading…