Skip to main content
eLearner.app
Module 6 · Lesson 4 of 529/50 in the course~10 min
Module lessons (4/5)

The Stringer interface

fmt.Stringer is probably the most-used standard interface in all of Go's standard library. It's the perfect demonstration of the power of small interfaces.

Definition

Go
type Stringer interface {
    String() string
}

A single signature. Any type that defines String() string satisfies it. And fmt recognizes this interface: when you pass a value to fmt.Print*, if it implements Stringer, the result of String() is used instead of the default representation.

Example

Go
type Point struct{ X, Y int }

func (p Point) String() string {
    return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}

fmt.Println(Point{1, 2})              // (1,2)
fmt.Printf("%v %s\n", Point{3, 4}, Point{5, 6})
// (3,4) (5,6)

The same value prints with custom formatting in %v, %s, Println, Sprint, log, etc. All just by defining one method.

When it makes sense

  • Types that model a domain (ids, codes, coordinates, enumerations).
  • Errors with a human-readable representation (see next lesson on error).
  • Types you log often and for which you want a fixed format.

Not for everything: if the type is simple and the default representation is fine, don't add noise.

Enum types: common pattern with iota

Go
type Status int

const (
    OK Status = iota
    WARN
    ERR
)

func (s Status) String() string {
    switch s {
    case OK:   return "OK"
    case WARN: return "WARN"
    case ERR:  return "ERR"
    default:   return "UNKNOWN"
    }
}

fmt.Println(OK, WARN, ERR)   // OK WARN ERR

Without String(), fmt.Println(OK) would print 0. With String(), it prints the symbolic name. The stringer tool generates this pattern automatically.

Watch out for infinite recursion

Go
type T int
func (t T) String() string {
    return fmt.Sprintf("%d", int(t))  // OK: cast to int breaks the recursion
}

Stringer on a pointer receiver

If String() has a pointer receiver, only *T satisfies Stringer. With fmt.Println(t) (a value), Go looks at the method set of T, doesn't find it, and uses the default format. To avoid this, value receiver is usually used for String() (it's a "view"; it doesn't modify anything).

Try it

Exercise#go.m6.l4.e1
Attempts: 0Loading…

Implement String() on Point so that it returns (X,Y).

Loading editor…
Show hint

Use `fmt.Sprintf` to format the fields.

Solution available after 3 attempts

Exercise#go.m6.l4.e2
Attempts: 0Loading…

Implement String() on a type Status (int): 0 → 'OK', 1 → 'WARN', otherwise → 'ERR'.

Loading editor…
Show hint

Switch on the receiver's value; remember to provide a default.

Solution available after 3 attempts

Quiz#go.m6.l4.e3
Ready

Which signature must the method have to satisfy fmt.Stringer?

Go
func (x T) ??? {}
Answer options

Recap

  • fmt.Stringer = interface { String() string }.
  • Implementing it changes how fmt.Print* represents the type.
  • Idiomatic for enums (iota), domain types, codes/identifiers.
  • Never call fmt.Sprintf("%s", t) on t inside String() (recursion).
  • Convention: value receiver for String() (no mutation, avoids "method-set surprises").