Skip to content
Theory · Interfaces · lesson 3 of 10

The Stringer interface

In the previous lesson an interface was a job ad. The fmt package has an ad of its own: "can describe itself as a string". When a value's type can do that, fmt prints the value the way the type describes it.

Let's start with a type that cannot do it yet. The declaration type Celsius float64 creates a new type for temperatures. Its underlying type is float64: they share values and arithmetic. But fmt.Println prints a temperature as a bare number, with no unit:

GO
package main

import "fmt"

type Celsius float64

func main() {
    t := Celsius(21.5)
    fmt.Println(t, t+1) // 21.5 22.5
}

Looking at 21.5, you can't tell whether it means degrees, meters or dollars. You want the program to print 21.5°C. You can declare methods on Celsius but not on float64: that type is not declared in your package. A method with a particular name is what changes how fmt prints a temperature.

The fmt.Stringer interface

The fmt package declares a Stringer interface with one method, String() string. Before printing a value, fmt.Println works like this:

  1. It checks whether the value's type implements the Stringer interface.
  2. If it does, it prints the string that String returned instead of the value itself.
  3. If there is no such method, it prints the value by the rules of its underlying type.

fmt.Print, fmt.Printf with the %v verb and the other functions of the package do the same.

A type implements Stringer implicitly, like any other interface. A method is enough if it has:

  • the name String;
  • no parameters;
  • a result of type string.

The String method

The String method of Celsius builds a string from the number and the degree sign:

GO
func (c Celsius) String() string {
    return fmt.Sprintf("%v°C", float64(c))
}

Let's read the method's body:

  • float64(c) turns the temperature into a plain number;
  • %v inserts that number into the template;
  • the characters °C follow the number.

Why the conversion is needed, the section on the common mistake will show.

A string without printing

The String method must return a string, not print it. For that, fmt has functions whose names start with S. Let's go through two of them.

fmt.Sprintf

The fmt.Sprintf function works like fmt.Printf but prints nothing: it returns the finished text as a string. Its format string works as in fmt.Printf: %v inserts a value, and %d an integer. You reach for it when a value goes in the middle of the text.

fmt.Sprint

In the same way, fmt.Sprint mirrors fmt.Print: it has no format string and turns its arguments into a string by the rules of fmt.Print. The expression fmt.Sprint(float64(c)) + "°C" gives the same string as the template "%v°C".

Printing a temperature

Now fmt prints a temperature with its unit:

GO
t := Celsius(21.5)
fmt.Println(t, t+1)     // 21.5°C 22.5°C
fmt.Println(float64(t)) // 21.5
s := fmt.Sprint(float64(t)) + " degrees"
fmt.Println(s) // 21.5 degrees

Let's walk through the example line by line:

  1. t := Celsius(21.5) creates a temperature.
  2. The first Println prints 21.5°C 22.5°C: the sum t+1 keeps the type Celsius and is also printed through String.
  3. The conversion float64(t) in the second Println gives a plain number of type float64. That type has no String method, so fmt prints it as a number: 21.5.
  4. The line with fmt.Sprint builds the text 21.5 degrees.
  5. The last Println prints that string.

Here the job-ad picture needs a correction. The skill belongs not to the number itself but to its type. A conversion changes the type of a value, and the type's methods go with it: the number is the same, but it can no longer describe itself.

Common mistake

Inside String, the value was passed to fmt.Sprint without a conversion:

GO
func (c Celsius) String() string {
    return fmt.Sprint(c) + "°C" // ✗
}
TEXT
fatal error: stack overflow

The program compiles but crashes the first time a temperature is printed. fmt.Sprint(c) sees that c implements Stringer and calls c.String(), which calls fmt.Sprint(c) again. The recursion does not end until the stack overflows. Inside String, pass fmt a value of the underlying type: float64(c) has no String method, and that breaks the loop.

Key points

  • A type with a String() string method implements fmt.Stringer, and fmt prints its values through that method.
  • fmt.Sprintf and fmt.Sprint print nothing; they return the finished string.
  • The conversion float64(t) drops the type's methods, and fmt prints the bare number.
  • Inside String, pass fmt a value of the underlying type, or the recursion overflows the stack.

Subscribe to take the course

The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.

Sign in another way