Printing and Formatting Output
You have already used fmt.Println. It is the first function every Go programmer learns and the last one they stop using. But fmt is doing considerably more than printing, and the twenty minutes you spend on this page will pay back every time you need to debug a struct, format a report, or build an error message.
The naming scheme
Once you see the pattern in fmt's function names, you can predict every one of them without looking it up.
prefix where it goes
────── ─────────────
(none) standard output
S returns a string
F writes to an io.Writer you choose
E (in errors) returns an error value
suffix how it formats
────── ──────────────
(none) values with spaces between operands when neither is a string
ln values with spaces between all operands, plus a newline
f formatted, using a template string with verbsCross them and you get the whole family:
| plain | ln | f | |
|---|---|---|---|
| to stdout | Print | Println | Printf |
| to a string | Sprint | Sprintln | Sprintf |
| to a writer | Fprint | Fprintln | Fprintf |
Plus fmt.Errorf, which formats like Printf but returns an error. You will use that one constantly in the error handling section.
fmt.Println("to the terminal")
msg := fmt.Sprintf("user %s has %d orders", name, count) // into a variable
fmt.Fprintf(os.Stderr, "warning: %v\n", err) // to standard error
fmt.Fprintf(w, "<h1>%s</h1>", title) // to an HTTP responseFprintf is the one that unlocks things. Any io.Writer works, which means files, network connections, HTTP responses, buffers, and compressors are all valid destinations with no change in how you write the call.
The verbs worth knowing
General purpose
type User struct {
Name string
Email string
Age int
}
u := User{"Shiva", "shiva@example.com", 28}
fmt.Printf("%v\n", u) // {Shiva shiva@example.com 28}
fmt.Printf("%+v\n", u) // {Name:Shiva Email:shiva@example.com Age:28}
fmt.Printf("%#v\n", u) // main.User{Name:"Shiva", Email:"shiva@example.com", Age:28}
fmt.Printf("%T\n", u) // main.UserThose four lines are the most useful debugging tools in Go.
%vis the default representation, and it works on absolutely anything%+vadds field names, which is what you almost always want for a struct%#vprints valid Go syntax you could paste back into code%Tprints the type, which settles arguments about what a value actually is
When a print statement is not telling you what you expected, switch %v to %+v first and %#v second. The difference between { 0 false} and {Name: Age:0 Active:false} is the difference between guessing and knowing.
Numbers
n := 255
fmt.Printf("%d\n", n) // 255
fmt.Printf("%b\n", n) // 11111111
fmt.Printf("%o\n", n) // 377
fmt.Printf("%x\n", n) // ff
fmt.Printf("%X\n", n) // FF
fmt.Printf("%c\n", 65) // A, as a character
fmt.Printf("%q\n", 65) // 'A', quoted character
fmt.Printf("%U\n", 0x1F600) // U+1F600f := 1234.5678
fmt.Printf("%f\n", f) // 1234.567800, six decimals by default
fmt.Printf("%.2f\n", f) // 1234.57
fmt.Printf("%e\n", f) // 1.234568e+03
fmt.Printf("%g\n", f) // 1234.5678, shortest form that round-trips%g is the one to use when you do not know the magnitude in advance. It switches between fixed and exponential notation and never prints trailing zeros.
Strings
s := "Go\tlang"
fmt.Printf("%s\n", s) // Go lang
fmt.Printf("%q\n", s) // "Go\tlang", quoted and escaped
fmt.Printf("%x\n", s) // 476f096c616e67, hex of each byte%q is quietly excellent for debugging. It shows you the quotes, so trailing spaces and stray tabs become visible instead of invisible:
input := "admin "
fmt.Printf("got %s\n", input) // got admin ← looks fine
fmt.Printf("got %q\n", input) // got "admin " ← there it isBooleans, pointers, and errors
fmt.Printf("%t\n", true) // true
fmt.Printf("%p\n", &u) // 0xc000010030
fmt.Printf("%v\n", err) // whatever err.Error() returnsWidth, alignment, and padding
The number between % and the verb controls the minimum width. A minus sign left-aligns, a zero pads with zeros.
fmt.Printf("|%10s|\n", "go") // | go|
fmt.Printf("|%-10s|\n", "go") // |go |
fmt.Printf("|%06d|\n", 42) // |000042|
fmt.Printf("|%8.2f|\n", 3.14159) // | 3.14|Which makes a readable table out of a loop:
products := []struct {
Name string
Price float64
Qty int
}{
{"Keyboard", 2499.00, 3},
{"Monitor", 18999.50, 1},
{"USB Cable", 299.99, 12},
}
fmt.Printf("%-12s %10s %5s\n", "PRODUCT", "PRICE", "QTY")
fmt.Println(strings.Repeat("-", 29))
for _, p := range products {
fmt.Printf("%-12s %10.2f %5d\n", p.Name, p.Price, p.Qty)
}PRODUCT PRICE QTY
-----------------------------
Keyboard 2499.00 3
Monitor 18999.50 1
USB Cable 299.99 12Widths can also come from arguments, using *:
width := 15
fmt.Printf("%*s\n", width, "right aligned")Making your own types print nicely
Implement String() string and every fmt function will use it automatically. This is the fmt.Stringer interface, and it is probably the most valuable single method you can add to a type.
type Money struct {
Paise int64
Currency string
}
func (m Money) String() string {
return fmt.Sprintf("%s %d.%02d", m.Currency, m.Paise/100, m.Paise%100)
}
price := Money{249950, "INR"}
fmt.Println(price) // INR 2499.50
fmt.Printf("total: %v\n", price) // total: INR 2499.50No configuration, no registration. fmt checks whether the value has a String method and calls it.
Never call fmt.Sprintf("%v", m) on the same type inside its own String method. %v will call String again, which calls %v again, and the program dies in infinite recursion. Format the individual fields instead, as the example above does.
Errors you will actually hit
Bad format strings do not stop compilation, they show up in your output.
fmt.Printf("%d and %d\n", 1) // 1 and %!d(MISSING)
fmt.Printf("%d\n", 1, 2) // 1
// %!(EXTRA int=2)
fmt.Printf("%d\n", "hello") // %!d(string=hello)
fmt.Printf("50%\n") // 50%!(NOVERB)That last one bites regularly. A literal percent sign has to be doubled:
fmt.Printf("battery at 50%%\n") // battery at 50%go vet checks Printf format strings against their arguments and reports mismatches at build time. It runs automatically as part of go test, which is one more reason to write tests early. Custom logging wrappers can be checked too, with go vet -printfuncs.
Choosing the right function
Just show me the value → fmt.Println(v)
Debug a struct → fmt.Printf("%+v\n", v)
What type is this really → fmt.Printf("%T\n", v)
Build a string for later → fmt.Sprintf(...)
Write to a file, socket, response → fmt.Fprintf(w, ...)
Report an error to the user → fmt.Errorf(...)
High volume application logging → log/slog, not fmtThat last line deserves emphasis. fmt is for output that a human reads right now: CLI output, debugging, building strings. For application logs that get collected and searched, use log/slog from the standard library, which produces structured key and value output. The going to production section covers it.
You can now declare values, convert them, combine them, and display them. Next, let's make programs that decide and repeat, starting with conditions.
How is this guide?
Last updated on
