-
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
fmtpackage, a type that implements it can be textually described with itsString()method. Thefmtpackage printing functions check if your types implement it to know how to print your values. ImplementingStringeris 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
Stringerfor 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*Dogpointer receiver, it could not be called for myDogvalue.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
intand 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
Dogstruct 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
Dogagain: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
Breedtype is not astringnor does it implement theStringerinterface.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
stringertool: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 Breedfrom ourdogpackage folder will generate abreed_string.gofile containing aStringmethod for theBreedtype 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=BreedNo more manual writing of
String()methods for enums.You’ll find all there is to know about
stringeron its GoDoc page. - Change your