Formatting & interpolation

Station 1.6 12 min Interchange: "{$name}" & sprintf & print_r → fmt.Printf

"Next train: {$dest}, platform {$platform}". You've typed thousands of these. Go doesn't have them. No Go string interpolates anything, so "{$dest}" is just six characters of text.

What Go has instead is the fmt package and a format string, which is PHP's sprintf with a few extra tricks. Your %d and %05.2f muscle memory comes along for the ride.

The print family

fmt names its functions in a pattern, so once you know the pattern you know them all:

What Prints Returns a string
Values, with spaces Println Sprintln
Values, as they are Print Sprint
A format string Printf Sprintf

The S versions are the ones you'll use most, because they hand back a string instead of printing it: Sprintf is sprintf, and Printf is printf. (There are F versions too, which write to a file or network connection. Lesson 7.4 covers those.)

msg := fmt.Sprintf("%s, platform %d", dest, platform)
fmt.Printf("Next train: %s\n", msg)

Printf doesn't add a newline, so end the format with \n. Println adds one, and puts spaces between its arguments.

Print and Sprint are the odd ones out. They add a space between two arguments only when neither of them is a string, so fmt.Sprint(1, 2, "a", 3, 4) is "1 2a3 4". Nobody remembers this rule. Use Sprintf when the layout matters.

The verbs

Verb Prints Example
%v any value, in its default format {Epping 3}
%+v structs with field names {Dest:Epping Platform:3}
%#v the value as Go code main.Departure{…}
%T the type main.Departure
%d an integer 42
%s a string Epping
%q a string in quotes, escaped "Köln"
%f, %.2f a float 3.14
%g a float, no trailing zeros 12.5
%t a bool true
%x hex ff
%% a literal % %

%v is the one to remember. It prints anything, from ints to structs to the slices and maps in Unit 2, and %+v is the nearest thing Go has to print_r. %q is great when debugging strings, because it shows the empty string as "" and makes stray spaces and \ns visible.

Widths and flags work as they do in PHP. A number between the % and the verb sets the width, a - left-aligns, and a 0 pads with zeros:

fmt.Sprintf("%5d|", 42)      // "   42|"
fmt.Sprintf("%-6s|", "Bank") // "Bank  |"
fmt.Sprintf("%02d", 7)       // "07"

Widths count characters, not bytes, so £ and ö take up one place each. After 1.5, that's a relief.

When you get a verb wrong

PHP's sprintf throws an ArgumentCountError if you pass too few arguments. Go's fmt never panics. It writes its complaint into the output instead:

ExpectedPlatform 3
GotPlatform %!d(MISSING)
go vetI did try to tell you.
fmt fails politely, and in public.

A wrong type gives you %!d(string=Epping), and a missing argument gives %!d(MISSING). Both turn up in your output rather than as an error, which means they reach users. Luckily go vet checks every format string against its arguments, and Submit runs vet, so you'll see fmt.Sprintf format %d has arg "Epping" of wrong type string well before anyone else does.

Mind the gap

PHP still wins on number_format. Go's fmt has no thousands separator and no locale-aware formatting. For 1,234,567.89 you either write a small helper or use the golang.org/x/text/message module. Money is usually stored in pence, as an int (floats and money don't mix), and formatted with %d.%02d, which is exactly what the exercise does.

For gluing a few strings together, + is still fine, and strconv.Itoa(n) turns an int into its digits. Reach for Sprintf once there's any formatting involved.

Recap

  • No interpolation. Sprintf builds a string, Printf prints one, and neither adds a newline.
  • %v prints anything, %+v adds field names, %q quotes strings, %T shows the type.
  • Widths and flags work like PHP's: %5d, %-20s, %02d. They count characters, not bytes.
  • A wrong verb gives %!d(…) in the output rather than an error. go vet catches it.

1No interpolation

PHPinterp.php
<?php
$dest = 'Epping';
$platform = 3;

echo "Next train: {$dest}, platform {$platform}";
GOinterp.go
dest := "Epping"
platform := 3

fmt.Printf("Next train: %s, platform %d\n", dest, platform)

msg := fmt.Sprintf("%s, platform %d", dest, platform)

Mind the gap

Go strings never interpolate. Printf prints, Sprintf returns the string, and both take the values after the format. Neither adds a newline, so put \n at the end.

2The verbs you already know

PHPverbs.php
<?php
sprintf('%d', 42);          // "42"
sprintf('%05d', 42);        // "00042"
sprintf('%.2f', 3.14159);   // "3.14"
sprintf('%-8s|', 'Bank');   // "Bank    |"
sprintf('%8s|', 'Bank');    // "    Bank|"
sprintf('%x', 255);         // "ff"
GOverbs.go
fmt.Sprintf("%d", 42)        // "42"
fmt.Sprintf("%05d", 42)      // "00042"
fmt.Sprintf("%.2f", 3.14159) // "3.14"
fmt.Sprintf("%-8s|", "Bank") // "Bank    |"
fmt.Sprintf("%8s|", "Bank")  // "    Bank|"
fmt.Sprintf("%x", 255)       // "ff"

Mind the gap

Go's verbs and padding come from C's printf, just like PHP's, so your sprintf muscle memory mostly works unchanged.

3Dumping values

PHPdump.php
<?php
$d = ['dest' => 'Epping', 'platform' => 3];

print_r($d);
var_dump($d);
echo gettype($d); // "array"
GOdump.go
d := Departure{Dest: "Epping", Platform: 3}

fmt.Printf("%v\n", d)  // {Epping 3}
fmt.Printf("%+v\n", d) // {Dest:Epping Platform:3}
fmt.Printf("%#v\n", d) // main.Departure{Dest:"Epping", Platform:3}
fmt.Printf("%T\n", d)  // main.Departure

Mind the gap

%v prints anything. Add + for field names, which is roughly print_r, or # for Go syntax, which is closer to var_export. %T prints the type.