We are thrilled to announce 📢 Kosli is now SOC 2 Type 2 compliant - Read more
✨ New Feature: Kosli Trails is live ✨ Create comprehensive audit trails for any DevOps activity - Read more
Printf Golang

A Deep Dive into fmt Printf in Golang

Michael Nyamande
Published February 24, 2023 in technology
clock icon 8 min read

Go is a simple but versatile programming language developed by Robert Griesemer at Google. It is one of the most sought-after programming languages and continues to grow in popularity. Critical to its adoption are Go’s core packages, which come bundled with the language.

The fmt package in Go is a library that helps with formatted I/O (input/output) operations. It can assist with printing output, scanning output, and formatting text for use in your applications. In this tutorial, you’ll take an in-depth look into fmt’s Printf function—a function for formatting text and outputting it to the console. You will learn how to use it to format text with Go’s different data types and special escape characters.

Basic Usage

Let’s start with a simple example. To use the Printf function, you must first import the fmt package. Then you can call the Printf function, passing a formatted string and any argument that will help you format the string:

package main

import "fmt"

func main() {
    const name = "Mike"
    fmt.Printf("My name is %s", name)
}

Output:

My name is Mike

The Printf function has the following signature:

func Printf(format string, a ...any)

The function itself is a variadic function, meaning that it takes a variable number of arguments. The first argument is a string, which can include special formatting specifiers (like %s) that will tell Go how to format the text. These specifiers act as placeholders for data that will be read from the arguments. Go uses variable substitution to replace the format specifiers with actual data from your arguments during runtime. In the example above, %s was replaced by the value of the name variable.

The second part, a, can be zero or more arguments of any type; this is denoted by the spread operator ...any. For example, if your formatted string requires more than one argument, it might look something like this:

fmt.Printf("%s is %d years old today", "Michael", 18)

This statement required two arguments to print and the output is Michael is 18 years old today. The sections below explore the different options available to format this output.

Format Specifiers

As mentioned above, you can use format specifiers as placeholders for your variables when using Printf. Each data type has its own unique specifier, which tells Go how to print out the variable. Below are the most common specifiers you’ll use when printing in Go:

%s - string values
%d - decimal values
%f - floating point values
%t - boolean values
%c - character values

Here’s an example showing how to use these placeholders to print different variables:

const name, age, character, boolean = "Mike", 18, 'c', true
fmt.Printf("String: %s \n", name)
fmt.Printf("Number: %d \n", age)
fmt.Printf("Character: %c \n", character)
fmt.Printf("Boolean: %t \n", boolean)

The output would look like this:

String: Mike 
Number: 18 
Character: c
Boolean: true

Precision

While %d is helpful for printing decimal values, there’s a range of different representations for number values. The most common issue when dealing with numbers is precision. The Printf function allows you to specify the precision of your numbers using %.xf, where x is the number of decimal places to take. For instance, %.2f is two decimal places and %.4f is four decimal places. Go also provides other specifiers for defining a number’s radix (number base) and other properties. Here are some common format specifiers when dealing with numbers:

%.2f - floating point to two decimal places
%.4f - floating point number to four decimal places
%e - prints numbers using scientific notation
%g - the shortest representation between %e or %f
%b - binary representation (base 2)
%o - octal representation (base 8)
%x - hexadecimal representation (base 16)
%X - hexadecimal but with capital letters

Here’s an example showing how you might use these specifiers:

number, floatingNumber := 238, 1234.575883939
fmt.Printf("Default: %f \n", floatingNumber)
fmt.Printf(".2f: %.2f \n", floatingNumber)
fmt.Printf(".4f: %.4f \n", floatingNumber)
fmt.Printf("Scientific: %e \n", floatingNumber)
fmt.Printf("Decimal: %d \n", number)
fmt.Printf("Binary: %b \n", number)
fmt.Printf("Octal: %o \n", number)
fmt.Printf("HexiDecimal: %X \n", number)

The output would look like this:

Default: 1234.575884 
.2f: 1234.58 
.4f: 1234.5759
Scientific: 1.234576e+03
Decimal: 238
Binary: 11101110
Octal: 356
HexiDecimal: EE

Escaping Characters

The Printf function also accepts standard escape characters for escape sequences, such as new lines and tabs. Below are the escape characters allowed by the function:

\a  Alert or bell
\b  Backspace
\t  Horizontal tab
\n  New line
\f  Form feed
\r  Carriage return
\v  Vertical tab
\' Single quote (only in rune literals)
\" Double quote (only in string literals)

Since the \ and % are used in your format operations, Go allows you to escape them using \\ and %% respectively.

Escaping text is demonstrated below:

fmt.Printf("\t \"Over %d %% of students passed.\" \n reported the exam board ", 50)

The output would be as follows:

         "Over 50 % of students passed." 
 reported the exam board

Printing Complex Types

The Printf function also allows you to print collections, like maps and slices, as well as composite types, such as structs. The easiest way to print out these is using %v. This formatting specifier prints the default representation of your data type. The Printf function also has a %#v specifier, which prints Go’s default representation of the data. An example of printing out a map is shown below:

names := []string{"Mike", "David", "George"}
fmt.Printf("Names: %v \n", names)
fmt.Printf("Names: %#v \n", names)

The output would look like this:

Names: [Mike David George] 
Names: []string{"Mike", "David", "George"} 

You can also print out structs using %v. With structs, as with maps, %v will print out only values. You can use %+v to print out both keys and values, or use %#v to print out the data type along with both key and value. Here’s an example:

type person struct {
    name string
    age  int
}

func main() {
    student := person{name: "Michael", age: 25}
    fmt.Printf("Student: %v \n", student)
    fmt.Printf("Student: %+v \n", student)
    fmt.Printf("Student: %#v \n", student)
}

Here’s the output:

Student: {Michael 25} 
Student: {name:Michael age:25} 
Student: main.person{name:"Michael", age:25}

%v is not limited only to collections and structs, but can be used with any valid type in Go. It is convenient when you are not sure what data type you will be handling. Go, however, provides an excellent way of determining your variable’s data type using the %T specifier, as illustrated below:

    const name, age = "Mike", 18
    names := []string{"Mike", "David", "George"}
    fmt.Printf("%T \n", name)
    fmt.Printf("%T \n", age)
    fmt.Printf("%T \n", names)

The output would look like this:

string 
int 
[]string

Padding

Printf also allows you to format your text with padding on both the right and left sides. You can do so using the specifier %xs, where x is the amount of padding to apply. This padding is added as width in runes, an untyped int32 representation in Go. %10s tells Go to pad your text by a width of ten runes on the left while, %-10s tells Go to pad it by a width of ten runes to the right. This padding is not limited to strings and can be applied to all other types. For example, you can pad floats %8f, booleans %-4t, and maps %-12v. Padding syntax can even be mixed with precision syntax. Here’s an example:

%8f - Pad by width of eight to the right
%8.2f - Pad by width of eight to the right and add two decimal places
%-10.4f - Pad by width ten to the left and use four decimal places
%6.f - Pad by width of six and use precision zero

You can also pad your text with zeros instead of a blank space by putting 0 in front of your number. For example, %040s will pad your text with zero for a width of forty runes. You can also pass the width as an argument instead of adding it to the specifier. This is done by adding * instead of a number on the specifier. For example, you might use %*s instead of %50s and then pass fifty as your first argument. This is illustrated below:

fmt.Printf("%*s", 50, "text")

Here’s the output:

                                              text

By using this syntax, it becomes easy to perform other alignment and formatting operations, such as centering text. To center your text, you need to know the width of your terminal. You can do this through the different commands available on Unix and Windows systems, or by using a Go package.

You can install the package with the following command:

go get github.com/kopoli/go-terminal-size

As for the width of the terminal, you can use the length of the text to calculate how much to pad the text on both sides. This is shown below:

import (
	"fmt"
	tsize "github.com/kopoli/go-terminal-size"
)

func main() {

	size, err := tsize.GetSize() // get width and height of our terminal
	width := size.Width

	if err == nil {
		text := "in the middle"
		padlen := (width - len(text)) / 2 
		fmt.Printf("%*s%s%*s\n", padlen, "", text, padlen, "")
	}
}

The output is as follows:

in the middle

Here’s how it looks on different screen sizes:

Small terminal size:
Small terminal size

Medium terminal size:
Medium terminal size

Large terminal size:
Large terminal size

Conclusion

In this article, you learned how to format printed output using Go’s fmt package. Printf allows you to print out variables of different data types, format for precision and escape characters, and align and format your output. The fmt package is a powerful tool for formatting text output. You can make your output easier to read and comprehend using Printf and its format specifiers. This is a useful skill to have if you are doing any kind of text manipulation, formatting, or logging. To learn more about using fmt, you can check out the official documentation.


ABOUT THIS ARTICLE

Published February 24, 2023, in technology

AUTHOR

Stay in the loop with the Kosli newsletter

Get the latest updates, tutorials, news and more, delivered right to your inbox
Kosli is committed to protecting and respecting your privacy. By submitting this newsletter request, I consent to Kosli sending me marketing communications via email. I may opt out at any time. For information about our privacy practices, please visit Kosli's privacy policy.
Kosli team reading the newsletter

Got a question about Kosli?

We’re here to help, our customers range from larges fintechs, medtechs and regulated business all looking to streamline their DevOps audit trails

Contact us
Developers using Kosli