Go Walkthrough: fmt

In the last post we looked at fast, primitive encoding using strconv but in this post we’ll take a higher level approach by using templates with the fmt package. The fmt package builds on top of our knowledge of primitive formatting options but packages it in a way that’s easier to use.

This post is part of a series of walkthroughs to help you understand the standard library better. While generated documentation provides a wealth of information, it can be difficult to understand packages in a real world context. This series aims to provide context of how standard library packages are used in every day applications. If you have questions or comments you can reach me at @benbjohnson on Twitter.

Important note before we continue

Before we go any further, I should note that the “fmt” package is pronounced “fumpt”. This surprises a lot of people.

I know… it sounds ridiculous.

However, if you get in a conversation with a fellow Gopher and refer to the “format” package or the “ef-em-tee” package then they may give you a blank stare.

What are templates?

The key concept in the fmt package is the format template. This is a string that contains the text you want to print plus some placeholders (called verbs) that tell fmt where to insert your variables.

These format strings are based on C’s printf() so they use a % symbol and a letter to indicate a placeholder. For example, “%s” is a placeholder for a string.

Verbs

The Printing section of the fmt godoc has a lengthy explanation of all the options for these verbs but I’ll give a summary of the most useful ones:

  • %v is a generic placeholder. It will automatically convert your variable into a string with some default options. This is typically useful when printing primitives such as strings or numbers and you don’t need specific options.
  • %#v prints your variable in Go syntax. This means you could copy the output and paste it into your code and it’ll syntactically correct. I find this most useful when working with structs and slices because it will print out the types and field names.
  • %T prints your variable’s type. This is really useful for debugging if your data is passed as an interface{} and you want to see what its concrete type.
  • %d prints an integer in base-10. You can do the same with %v but this is more explicit.
  • %x and %X print an integer in base-16. One nice trick though is that you can pass in a byte slice and it’ll print each byte as a two-digit hex number.
  • %f prints a floating point number without an exponent. You can do the same with %v but this becomes more useful when we add width and precision flags.
  • %q prints a quoted string. This is useful when your data may have invisible characters (such as zero width space) because the quoted string will print them as escape sequences.
  • %p prints a pointer address of your variable. This one is really useful when you’re debugging code and you want to check if different pointer variables reference the same data.

Width & precision

We can make formatting more useful by adding various flags to the verb. This is especially important for floating-point numbers where you typically need to round them to a specific number of decimal places.

The precision can be specified by adding a period and a number after the % sign. For example, we can use %.2f to specify two decimal places of precision so formatting 100.567 would print as “100.57”. Note that the second decimal place is rounded.

The width specifies the total number of characters your formatted string will take up. If your formatted value is less than width then it will pad with spaces. This is useful when you’re printing tabular data and you want fields to line up. For example, we can add to our previous format and set the width to 8 by adding the number before the decimal place: %8.2f. Printing 100.567 with this format will return “••100.57” (where • is a space).

We can map this out in a table to show how it works for various widths and precisions:

%8.0f ➡ "     101"
%8.1f ➡ " 100.6"
%8.2f ➡ " 100.57"
%8.3f ➡ " 100.567"

Left alignment

In our previous example our values were right-aligned. This works well for financial applications where you may want the decimal places lined up on the right. However, if you want to left-align your fields you can use the “-” flag:

%-8.0f ➡ "101     "
%-8.1f ➡ "100.6 "
%-8.2f ➡ "100.57 "
%-8.3f ➡ "100.567 "

Zero padding

Sometimes you want to pad using zeros instead of spaces. For instance, you may need to generate fixed-width strings from an number. We can use the zero (‘0’) flag to do this. Printing the number 123 with an 8-byte width and padded with zeros looks like this:

%08d ➡ "00000123"

Spacing

When you print out byte slices using the %x verb it comes out as one giant string of hex numbers. You can delimit the bytes with a space by using the space flag (‘ ‘).

For example, formatting []byte{1,2,3,4} with and without the space flag:

%x  ➡ "01020304"
% x ➡ "01 02 03 04"

Other verbs & flags

There are still a bunch more verbs and flags that I didn’t cover and you can read about them in detail in the Printing section of the fmt godoc. The ones I presented here are the ones I use the vast majority of the time.

Printing

The primary use of the fmt package is to format strings. These formatting functions are grouped by their output type — STDOUT, io.Writer, & string.

Each of these groups has 3 functions — default formatting, user-defined formatting, and default formatting with a new line appended.

Printing to STDOUT

The most common use of formatting is to print to a terminal window through STDOUT. This can be done with the Print functions:

func Print(a ...interface{}) (n int, err error)
func Printf(format string, a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)

The Print() function simply prints a list of variables to STDOUT with default formatting. The Printf() function allows you to specify the formatting using a format template. The Println() function works like Print() except it inserts spaces between the variables and appends a new line at the end.

I typically use Printf() when I need specific formatting options and Println() when I want default options. I almost always want a new line appended so I don’t personally use Print() much. One exception is if I am requesting interactive input from a user and I want the cursor immediately after what I print. For example, this line:

fmt.Print("What is your name? ")

Will output to the terminal with the cursor immediately after the last space:

What is your name? █

Printing to io.Writer

If you need to print to a non-STDOUT output (such as STDERR or a buffer) then you can use the Fprint functions. The “F” in these functions comes FILE which was the argument type used in C’s fprintf() function.

func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)

These functions are just like the Print functions except you specify the writer as the first argument. In fact, the Print functions are just small wrappers around the Fprint functions.

I typically abstract STDOUT away from my components so I use Fprint functions a lot. For example, if I have a component that logs information then I’ll add a LogOutput field:

type MyComponent struct {
LogOutput io.Writer
}

That way I can attach STDOUT when I use it in my application:

var c MyComponent
c.LogOutput = os.Stdout

And I can attach a buffer when I use it in my tests so I can validate it:

var c MyComponent
var buf bytes.Buffer
c.LogOutput = &buf
c.Run()
if strings.Contains(buf.String(), "component finished") {
t.Fatalf("unexpected log output: %s", buf.String())
}

Formatting to a string

Sometimes you need to work with strings instead of writers. You could use the the Fprint functions to write to a buffer and convert it to a string but that’s a lot of work. Fortunately there are the Sprint convenience functions:

func Sprint(a ...interface{}) string
func Sprintf(format string, a ...interface{}) string
func Sprintln(a ...interface{}) string

The “S” here stands for “String”. These functions take the same arguments as the Print() functions except they return a string.

While these functions are convenient, they can be a bottleneck if you’re frequently generating strings. If you profile your application and find that you need to optimize it then reusing a bytes.Buffer with the Fprint() functions can be much faster.

Error formatting

One last formatting function that doesn’t quite fit into the other groups is Errorf():

func Errorf(format string, a ...interface{}) error

This is literally just a wrapper for errors.New() and Sprintf():

func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}

Scanning

We can also read our formatted data and parse it back into our original variables. This is called scanning. These are also broken up into similar groups as the printing functions — read from STDIN, read from an io.Reader, and read from a string.

Disclaimer

I personally almost never use the scan functions in my applications. Most input for my applications come from CLI flags, environment variables, or API calls. These are typically already formatted as a basic primitives and I can use strconv to parse them. That being said, I’ll do my best to explain these with limited experience.

Scanning from STDIN

The basic Scan functions operate on STDIN just as the basic Print functions operate on STDOUT. These also come in 3 types:

func Scan(a ...interface{}) (n int, err error)
func Scanf(format string, a ...interface{}) (n int, err error)
func Scanln(a ...interface{}) (n int, err error)

The Scan() function reads in space-delimited values into variables references passed into the function. It treats newlines as spaces. The Scanf() does the same but lets you specify formatting options using a format string. The Scanln() function works Scan() except that it does not treat newlines as spaces.

For example, you can use Scan() to read in successive values:

var name string
var age int
if _, err := fmt.Scan(&name, &age); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Printf("Your name is: %s\n", name)
fmt.Printf("Your age is: %d\n", age)

You can run this in your main() function and execute:

$ go run main.go
Jane 25
Your name is: Jane
Your age is: 25

Again, these functions aren’t terribly useful because you will likely pass in data via flags, environment variables, or configuration files.

Scanning from io.Reader

You can use the Fscan functions to scan from a reader besides STDIN:

func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)

Scanning from a string

Finally, you can use the Sscan functions to scan from an in-memory string:

func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)

Stringers

Go has a style convention where the names of interfaces are created by taking the name of the interface’s function and adding “er”. Because of this convention we get funny names like Stringer.

The Stringer interface allows objects to handle how they convert themselves into a human readable format:

type Stringer interface {
String() string
}

This is used all over the place in the standard library such as net.IP.String() or bytes.Buffer.String(). This format is used when using the %s formatting verb or when passing a variable into Print().

Formatting to Go

There is also a related interface called GoStringer which allows objects to encode themselves in Go syntax.

type GoStringer interface {
GoString() string
}

This is used when the “%#v” verb is used in print functions. It’s uncommon to need to use this interface since the default implementation of that verb usually gives a good representation.

User-defined types

One of the more obscure parts of the fmt package is in user-defined formatter and scanner types. These give you full control over how an object gets formatted or scanned when using the Printf and Scanf functions.

Formatters

Custom formatters can be added to your types by implementing fmt.Formatter:

type Formatter interface {
Format(f State, c rune)
}

The Format() function accepts a State object which lists the options specified in the verb associated with your variable. This includes the width, precision, and other flags. The c rune specifies the character used in the verb.

Concrete example

A concrete example will make more sense. Honestly, I couldn’t think of a good example of when to use this so we’ll make a silly one. Let’s say we have a Header type that is simply text that we want to be able to decorate using characters before and after. Here’s our example usage:

hdr := Header(“GO WALKTHROUGH”)
fmt.Printf(“%2.3s\n”, hdr)

Here we’re just printing hdr with the verb “%2.3s” meaning that we want 2 characters before the header and 3 characters after. Just for fun we’ll use “#” before the text and snowmen (☃) after the text. I know… dumb example but bear with me.

Here’s our Header type with its custom Formatter implementation:

// Header represents formattable header text.
type Header string
// Format decorates the header with pounds and snowmen.
func (hdr Header) Format(f fmt.State, c rune) {
wid, _ := f.Width()
prec, _ := f.Precision()
        f.Write([]byte(strings.Repeat("#", wid)))
f.Write([]byte(hdr))
f.Write([]byte(strings.Repeat("☃", prec)))
}

In our Format() function we’re extracting the width and precision from the verb (2 & 3, respectively) and then printing back into the State object. This will get written to our Printf() output.

You can run this example here and see the following output:

##GO WALKTHROUGH☃☃☃

Inside the standard library the formatter is used for special math types such as big.Float & big.Int. Those seem like legitimate use cases but I really can’t think of another time to use this.

Scanners

On the scanning side there is a Scanner interface:

type Scanner interface {
Scan(state ScanState, verb rune) error
}

This works similarly to the Formatter except that you’re reading from your state instead of writing to it.

Review

Let’s review some of the do’s and don’ts of using the fmt package:

  • Do pronounce the package as “fumpt”!
  • Do use the %v placeholder if you don’t need formatting options.
  • Do use the width & precision formatting options — especially for floating-point numbers.
  • Don’t use Scan functions in general. There’s probably a better method of user input.
  • Do define String() functions on your types if the default implementation isn’t useful.
  • Don’t use custom formatters & scanners. There’s not a lot of good use cases unless you’re implementing mathematical types.
  • Don’t use fmt if you’ve found it to be a performance bottleneck. Drop down to strconv when you need to optimize. However, only do this after profiling!

Conclusion

Printing output into a human readable format is a key part of any application and the fmt package makes it easy to do. It provides a variety of formatting options and verbs depending on the type of data you’re displaying. It also provides scanning functions and custom formatting if you ever happen to need that.

If you liked this, click the💚 below so other people will see this here on Medium.

Show your support

Clapping shows how much you appreciated Ben Johnson’s story.