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:
Medium 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.