-
All about Go's Stringer interface
One of Go’s simplest and most used interfaces is
Stringer
:type Stringer interface { String() string }
Defined by the
fmt
package, a type that implements it can be textually described with itsString()
method. Thefmt
package printing functions check if your types implement it to know how to print your values. ImplementingStringer
is useful for many purposes, such as for logging and debugging.Well, there’s not much to it, right? So I thought.
Pointer and value receivers
A couple days ago, I had implemented
Stringer
for a type of mine:package dog import "fmt" type Dog struct { name string breed string } func (d *Dog) String() string { return fmt.Sprintf("My name is %s, I'm a %s! Woof!", d.name, d.breed) }
When I tried printing a
Dog
:package main import "fmt" func main() { d := dog.Dog{"Rex", "poodle"} fmt.Print(d) }
For some reason, Go refused to work and output
{Rex poodle}
instead of mycutecustom message.After some googling, I found the explanation in the Effective Go page (actually, a link to it in Stack Overflow, but you know what I mean):
The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers. This is because pointer methods can modify the receiver; invoking them on a copy of the value would cause those modifications to be discarded.
Since my
String()
method was implemented on a*Dog
pointer receiver, it could not be called for myDog
value.There are at least two possible solutions for this problem:
- Change your
String()
method to a value receiver so that it may be invoked for both values and pointers; - Keep the pointer receiver and always pass a pointer when you want your
String()
method to be invoked.
Solution #2 is specially recommended if copying your value is too costly.
After changing my method to a value receiver, I finally got the message I wanted:
My name is Rex, I'm a poodle! Woof!
Stringer for enumerations
An idiomatic manner to building enums in Go is to define an alias type for
int
and then define constants correspoding to the possible enum values with iota:package dog type Breed int const ( Poodle Breed = iota Beagle Labrador Pug )
Let’s change our
Dog
struct to use this enum:type Dog struct { name string breed Breed } // we now have a value receiver func (d Dog) String() string { return fmt.Sprintf("My name is %s, I'm a %s! Woof!", d.name, d.breed) }
If we try to print our example
Dog
again:func main() { d := dog.Dog{"Rex", dog.Poodle} fmt.Print(d) }
We get a weird message:
My name is Rex, I'm a %!s(main.Breed=0)! Woof!
This happens because the
Breed
type is not astring
nor does it implement theStringer
interface.To solve this problem, we could implement the
String()
method forBreed
:func (b Breed) String() string { switch b { case Poodle: return "poodle" case Beagle: return "beagle" // and so on... } }
This is very error-prone. We would need to change this method each time we add, remove or change an enum value.
To overcome this issue, we can use Go’s
stringer
tool:Stringer is a tool to automate the creation of methods that satisfy the fmt.Stringer interface.
You can download and install it with:
go get https://godoc.org/golang.org/x/tools/cmd/stringer`
Running
stringer -type Breed
from ourdog
package folder will generate abreed_string.go
file containing aString
method for theBreed
type which is exactly as we would expect.We can even add a Go generate directive to our file so that this file is programatically created with
go generate
://go:generate stringer -type=Breed
No more manual writing of
String()
methods for enums.You’ll find all there is to know about
stringer
on its GoDoc page. - Change your