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
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
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
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 ERRWithout 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
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
Implement String() on Point so that it returns (X,Y).
Show hint
Use `fmt.Sprintf` to format the fields.
Solution available after 3 attempts
Implement String() on a type Status (int): 0 → 'OK', 1 → 'WARN', otherwise → 'ERR'.
Show hint
Switch on the receiver's value; remember to provide a default.
Solution available after 3 attempts
Which signature must the method have to satisfy fmt.Stringer?
func (x T) ??? {}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)ontinsideString()(recursion). - Convention: value receiver for
String()(no mutation, avoids "method-set surprises").