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:
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:
- It checks whether the value's type implements the
Stringerinterface. - If it does, it prints the string that
Stringreturned instead of the value itself. - 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:
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;%vinserts that number into the template;- the characters
°Cfollow 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:
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 degreesLet's walk through the example line by line:
t := Celsius(21.5)creates a temperature.- The first
Printlnprints21.5°C 22.5°C: the sumt+1keeps the typeCelsiusand is also printed throughString. - The conversion
float64(t)in the secondPrintlngives a plain number of typefloat64. That type has noStringmethod, sofmtprints it as a number:21.5. - The line with
fmt.Sprintbuilds the text21.5 degrees. - The last
Printlnprints 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:
func (c Celsius) String() string {
return fmt.Sprint(c) + "°C" // ✗
}fatal error: stack overflowThe 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() stringmethod implementsfmt.Stringer, andfmtprints its values through that method. fmt.Sprintfandfmt.Sprintprint nothing; they return the finished string.- The conversion
float64(t)drops the type's methods, andfmtprints the bare number. - Inside
String, passfmta 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.