GopherAcademy
Miki Tebeka
Dec 8, 2018 6 min read

A Dive Into the `fmt` Package

We usually use the fmt package without giving it much thought. A fmt.Printf here, a fmt.Sprintf there and on we go. However, if you’ll take a closer look, you’ll be able to get much more out of it.

Since Go is used a lot to write servers or services, our main debugging tool is the logging system. The log package provides log.Printf which has the same semantics as fmt.Printf. Good and informative log messages are worth their weight in gold and adding some formatting support to your data structure will add valuable information to your log messages.

Formatting Output

Go fmt functions supports several verbs, the most common ones are %s for strings, %d for integers and %f for floats. Let’s explore some more verbs.

%v & %T

%v will print any Go value and %T will print the type of the variable. I use these verbse when debugging.

1
2
var e interface{} = 2.7182
fmt.Printf("e = %v (%T)\n", e, e) // e = 2.7182 (float64)

Width

You can specify the width of a printed number, for example

1
fmt.Printf("%10d\n", 353)  // will print "       353"

You can also specify the width as a parameter to Printf by specifying the width as *. For example:

1
fmt.Printf("%*d\n", 10, 353)  // will print "       353"

This is useful when you print list of numbers and would like to align them to the right for comparison.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// alignSize return the required size for aligning all numbers in nums
func alignSize(nums []int) int {
	size := 0
	for _, n := range nums {
		if s := int(math.Log10(float64(n))) + 1; s > size {
			size = s
		}
	}

	return size
}

func main() {
	nums := []int{12, 237, 3878, 3}
	size := alignSize(nums)
	for i, n := range nums {
		fmt.Printf("%02d %*d\n", i, size, n)
	}
}

will print

1
2
3
4
00   12
01  237
02 3878
03    3

Making it easier for us to compare the numbers.

Reference by Position

If you’re referencing a variable several times inside a format string, you can reference by position using %[n] where n is the index of the parameter (1 based).

1
fmt.Printf("The price of %[1]s was $%[2]d. $%[2]d! imagine that.\n", "carrot", 23)

will print

1
The price of carrot was $23. $23! imagine that.

%v

%v will print a Go value, it can be modified with + prefix to print field names in a struct and with # prefix to print field names and type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Point is a 2D point
type Point struct {
	X int
	Y int
}

func main() {
	p := &Point{1, 2}
	fmt.Printf("%v %+v %#v \n", p, p, p)
}

will print

1
&{1 2} &{X:1 Y:2} &main.Point{X:1, Y:2} 

I tend to use the %+v verb most.

fmt.Stringer & fmt.Formatter

Sometimes you’d like a finer control on how your objects are printed. For example you’d like one string representation for an error when it is shown to the user and another, more detailed, when it is written to log.

To control how your objects are printed, you need to implement fmt.Formatter interface and optionally fmt.Stringer.

One good example is how the excellent github.com/pkg/errors makes use of fmt.Formatter. Say you’d like to load our configuration file with and you have an error. You can print a short error to the user (or return it in API …) and print a more detailed error to the log.

1
2
3
4
5
cfg, err := loadConfig("/no/such/config.toml")
if err != nil {
	fmt.Printf("error: %s\n", err)
	log.Printf("can't load config\n%+v", err)
}

this will emit to the user

1
error: can't open config file: open /no/such/file.toml: no such file or directory

and to the log file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
2018/11/28 10:43:00 can't load config
open /no/such/file.toml: no such file or directory
can't open config file
main.loadConfig
	/home/miki/Projects/gopheracademy-web/content/advent-2018/fmt.go:101
main.main
	/home/miki/Projects/gopheracademy-web/content/advent-2018/fmt.go:135
runtime.main
	/usr/lib/go/src/runtime/proc.go:201
runtime.goexit
	/usr/lib/go/src/runtime/asm_amd64.s:1333

Here’s a small example. Say you have an AuthInfo struct for a user

1
2
3
4
5
6
// AuthInfo is authentication information
type AuthInfo struct {
	Login  string // Login user
	ACL    uint   // ACL bitmask
	APIKey string // API key
}

You’d like to limit the chances that the APIKey will be printed out (say when you log). You can print a mask (*****) instead of the key

1
2
3
const (
	keyMask = "*****"
)

First the easy case fmt.Stringer.

1
2
3
4
5
6
7
8
// String implements Stringer interface
func (ai *AuthInfo) String() string {
	key := ai.APIKey
	if key != "" {
		key = keyMask
	}
	return fmt.Sprintf("Login:%s, ACL:%08b, APIKey: %s", ai.Login, ai.ACL, key)
}

And now fmt.Formatter which gets a fmt.State and a rune for the verb. fmt.State implements io.Writer, enabling you to write directly to it.

To know all the fields available in a struct, you can use the reflect. package. This will make sure your code works even when AuthInfo changes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var authInfoFields []string

func init() {
	typ := reflect.TypeOf(AuthInfo{})
	authInfoFields = make([]string, typ.NumField())
	for i := 0; i < typ.NumField(); i++ {
		authInfoFields[i] = typ.Field(i).Name
	}
	sort.Strings(authInfoFields) // People are better with sorted data
}

And now you’re ready to implement fmt.Formatter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Format implements fmt.Formatter
func (ai *AuthInfo) Format(state fmt.State, verb rune) {
	switch verb {
	case 's', 'q':
		val := ai.String()
		if verb == 'q' {
			val = fmt.Sprintf("%q", val)
		}
		fmt.Fprint(state, val)
	case 'v':
		if state.Flag('#') {
			// Emit type before
			fmt.Fprintf(state, "%T", ai)
		}
		fmt.Fprint(state, "{")
		val := reflect.ValueOf(*ai)
		for i, name := range authInfoFields {
			if state.Flag('#') || state.Flag('+') {
				fmt.Fprintf(state, "%s:", name)
			}
			fld := val.FieldByName(name)
			if name == "APIKey" && fld.Len() > 0 {
				fmt.Fprint(state, keyMask)
			} else {
				fmt.Fprint(state, fld)
			}
			if i < len(authInfoFields)-1 {
				fmt.Fprint(state, " ")
			}
		}
		fmt.Fprint(state, "}")
	}
}

Let’s try it out:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
ai := &AuthInfo{
	Login:  "daffy",
	ACL:    ReadACL | WriteACL,
	APIKey: "duck season",
}
fmt.Println(ai.String())
fmt.Printf("ai %%s: %s\n", ai)
fmt.Printf("ai %%q: %q\n", ai)
fmt.Printf("ai %%v: %v\n", ai)
fmt.Printf("ai %%+v: %+v\n", ai)
fmt.Printf("ai %%#v: %#v\n", ai)

which will emit

1
2
3
4
5
6
Login:daffy, ACL:00000011, APIKey: *****
ai %s: Login:daffy, ACL:00000011, APIKey: *****
ai %q: "Login:daffy, ACL:00000011, APIKey: *****"
ai %v: {3 ***** daffy}
ai %+v: {ACL:3 APIKey:***** Login:daffy}
ai %#v: *main.AuthInfo{ACL:3 APIKey:***** Login:daffy}

Conculsion

The fmt package has many capabilities other than the trivial use. Once you’ll familiarize yourself with these capabilities, I’m sure you find many interesting uses for them.

You can view the code for this post here.

About the Author

Hi there, I’m Miki, nice to e-meet you ☺. I’ve been a long time developer and have been working with Go for about 8 years now. I write code professionally as a consultant and contribute a lot to open source. Apart from that I’m a book author, an author on LinkedIn learning, one of the organizers of GopherCon Israel and an instructor. Feel free to drop me a line and let me know if you learned something new or if you’d like to learn more.