Using Golang's Stringer tool

Straight from the documentation:

Stringer is a tool to automate the creation of methods that satisfy the fmt.Stringer interface. Given the name of a (signed or unsigned) integer type T that has constants defined, stringer will create a new self-contained Go source file implementing:

func (t T) String() string

The file is created in the same package and directory as the package that defines T. It has helpful defaults designed for use with go generate.

Stringer is among other Golang tools which help us in writing good go code. This is a CLI tool which is used via go generate. Go generate is another tool that comes with go. It is used for auto-generating code. You can explore more about it in their documentation.

So, we use the stringer command with go generate to generate the code necessary to implement fmt’s stringer interface. Conceptually this is very simple. But the documentation for stringer is, well there is no documentation. There is only a help flag in the stringer command which only lists the available flags we can use with stringer. These flags are very useful but are ignored by many people. They give us control over how the final output string is formatted.

What stringer is used for

Stringer is generally used to implement the concept of enumeration while avoiding hard-coding of the string values. Enumeration or enum is when you want to define a finite and fixed set of values of a given type. For example, types of dishes in a menu (appetizers, main course, desserts), types of pokemon (grass, water, fire, ..), etc.

So, when we want to use these defined sets of values, we should avoid hard coding them as it can lead to human errors like spelling mistakes. If we use something like stringer, we will generate the values once and make sure there are no errors. After that, we can just use them without worrying about human errors.

Hands-on

The types that we define as enums will be extending int. So, when we declare the variables for each element, they will hold int values (value1 = 1, value2 = 2, etc). These int values will be used by stringer internally to index the values. But, we don’t have to manually initialise the int value for each of the elements. Go provides an identifier called iota to incrementally initialise int values. Let’s look at an example for iota.

type Sample int

const (
	value1 Sample = iota
	value2
	value3
	value4
)

// or if you want the values
// to start from 1

const (
	value5 Sample = iota + 1
	value6
	value7
	value8
)

Let's go over each of the flags stringer has and see how they can be used with some examples.

-type

-type is a compulsory argument which defines which type the string belongs to. This argument also supports a list of comma-separated type names. The String() method will be generated for all the given types. But this method’s definition will be put in a separate file. This file will by default be named <type>_string.go . Let’s look at an example for the -type :

type HeroType int
type AttackType int

const (
	Strength HeroType = iota + 1
	Agility
	Intelligence
)

const (
	Melee AttackType = iota + 1
	Ranged
)

//go:generate stringer -type=HeroType,AttackType

Both the String() definitions will be put in a single file called herotype_string.go . If we want them to be in separate files, we will need to have two go generate comments like this:

//go:generate stringer -type=HeroType
//go:generate stringer -type=AttackType

This will create two files: herotype_string.go and attacktype_string.go . To generate these files, we need to run the command go generate in the same directory. This will create the files. And every time we run go generate , these files will be regenerated. So, it is important that we do not edit these generated files manually as they will get overwritten.

-output

-output flag is used to explicitly specify the name of the file what will be generated. This will override the default name <type>_string.go . That’s pretty much all this flag does. So, for the above example, if we wanted to put both the type’s generations into a single file called hero-attack_string.go, we can use the -output flag like this:

//go:generate stringer -type=HeroType,AttackType -output=hero-attack_string.go

This will generate the single hero-attack_string.go with both the String() definitions.

-trimprefix

Sometimes, we would want to name the enum variables descriptively with a prefix, to make the code more readable (Ex: HeroTypeAgility instead of just Agility). But this exact name would get reflected in the final output string. We can use the -trimprefix to avoid this and trim the prefix before generating the final string output. This will trim the specified prefix and only use the rest of the text as the value.

type HeroType int

//go:generate stringer -type=HeroType -trimprefix=HeroType
const (
    HeroTypeStrength HeroType = iota + 1
    HeroTypeAgility
    HeroTypeIntelligence
)

This will generate the String() function which return the above values with “HeroType” trimmed off: “Strength”, “Agility”, “Intelligence”

-linecomment

This flag, like trimprefix is also used for customising what the String() function returns.trimprefix only lets you trim of the prefix of the value, but this flag lets you set WHATEVER value you want to a particular object of that type. This custom value is set by adding a comment on the same line as the object, with the value you want to set. Example:

type Artist int

//go:generate stringer -type=Artist -linecomment
const (
    ArtistLedZeppelin Artist = iota + 1 // led-zepplin
    ArtistPinkFloyd                     // pink-floyd
    ArtistPostMalone                    // post-malone
)

This will generate enums with the values: “led-zeppelin”, “pink-floyd” and “post-malone”. This way, we can have “-” in the string value. linecomment is generally used in cases like this when we want to have special characters, start the value with a lower case, keep the variable camel-case and the values snake-case etc.

NOTE: The comment has to be on the same line. And the space after // is ignored

-tags

This flag is used to specify the build tags. These build tags are used for conditionally generate the String() function. I am not very sure about how to specifically use this. I tried to find out, but couldn’t find much online. If you know any examples, please comment below.

These are the flags available in stringer. This is a fairly simple tool to use. Hope this post give you more clarity about it.