Guide to Printing to the Console in Go
Language
- unknown
by James Smith (Golang Project Structure Admin)
Most programs will need to emit some kind of output if they’re going to be useful to the people using them.
So today we will look at the various ways to print content to the console in Go, focusing on four related functions in particular.
Table of Contents
Introducing the Fmt Package
The Go standard library’s functions for printing to the console that we will look at are all contained within the "fmt"
package.
The package name "fmt"
is short for the word “format”, since the functions handle the presentation of output as well as actually emitting it to the console.
Using the Print Function
The simplest function that can be used to emit output to the console is fmt.Print
, which takes an arbitrary number of arguments of any type and converts them to human-readable strings, so that they can be concatenated together and printed out to the console.
package main
import "fmt"
func main() {
fmt.Print(99, "test", []string{"cat", "dog", "goldfish"})
}
Running the program above should print the following output:
99test[cat dog goldfish]
We can see how the arguments have been concatenated together without putting any whitespace between them.
Return Values of the Print Function
The fmt.Print
function has two return values. The first is an integer, which holds the number of bytes written to the console, and the second is an error value.
However, these return values are rarely used, since it’s generally safe to assume that a print operation will complete successfully.
That is why, when we called fmt.Print
, we didn’t store its return values in variables or check if an error had occurred. However, if you want to be extra safe, then you can always do that.
Printing to Any Writer With the Fprintf Function
If you look at the code for the fmt.Print
function in the Go standard library, you will see that it is defined like so (at least in version 1.19 of the programming language, which I’m using):
func Print(a ...any) (n int, err error) {
return Fprint(os.Stdout, a...)
}
This is because fmt.Fprint
is a more general version of fmt.Print
. Its first argument is an io.Writer
, which specifies where the output will be sent. Using the os.Stdout
constant as this argument will send the output to the console.
The example below shows how a file handle can be used as an io.Writer
, enabling the fmt.Fprint
function to write to a file:
package main
import (
"fmt"
"os"
)
func main() {
tempFile, err := os.CreateTemp("", "")
if err != nil {
panic(err)
}
defer tempFile.Close()
fmt.Fprint(tempFile, "This is", " a test")
tempFileContent, err := os.ReadFile(tempFile.Name())
if err != nil {
panic(err)
}
fmt.Print(string(tempFileContent))
}
The os.CreateTemp
function produces a new file in the os.TempDir
directory, returning a handle to it.
We can then use this handle as the first argument of the fmt.Fprint
function, allowing us to print content to it, just as we had to the console previously.
Finally, we read the content of the file, simply in order to check that it’s been printed correctly, and output it to the console with the regular fmt.Print
function.
Using the Println Function
The fmt.Println
function is very similar to the fmt.Print function except that it also emits a newline after printing its arguments to the console.
The three statements within the main function shown below therefore each produce exactly the same output:
package main
import "fmt"
func main() {
fmt.Println("example")
fmt.Print("example", "\n")
fmt.Print("example" + "\n")
}
A backslash followed by the letter 'n'
is how a newline is written in Go strings. This escape sequence is necessary because ordinary Go strings, enclosed in double quotes, do not span multiple lines.
However, it is also possible to write a multiline string by enclosing it in backticks instead of double quotes. This form of string does not interpret any escape sequences.
Introducing the Printf Function
Anyone who’s used the C programming language will understand immediately what the fmt.Printf
function does — and how extremely powerful it can be — because calling the printf function (defined in the <stdio.h>
header file) is the main way that output is printed to the console in C.
The fmt.Printf
function always takes at least one argument, which is called the format string, but it also takes an arbitrary number of optional arguments. The format string determines how the optional arguments should be printed.
Understanding the Format String Used in the Printf Function
The format string is just a collection of runes — in other words, characters — like any other string, most of which will be printed out exactly as they’re written, but it also contains placeholders that will be replaced by one of the optional arguments.
Placeholders are shown by the '%'
character followed by another character, which is known as a verb, that indicates the type of the value that will be printed to the console.
For example, the "%s"
placeholder will be replaced by a string, since the 's'
character stands for the word string. On the other hand, the "%d"
placeholder will always be replaced by an integer, since the 'd'
character stands for the word decimal, which is how the number will be printed, in base 10.
Matching Arguments In the Printf Function
Both the number and type of the optional arguments passed to the fmt.Printf
function must match the placeholders in the format string.
Let’s consider an example: if you were to pass the format string "my name is %s and I am %d years of age"
to the fmt.Printf
function, you would also need to pass two more arguments, the first being a string and the second an integer, because that’s what the placeholders specify.
If you were to pass two integers as the second and third arguments instead, the fmt.Printf
function would fail to function correctly. It would not convert the first integer to a string: it would have expected you to do that already (perhaps by using the strconv.Itoa
function), since you specified that you were going to be passing a string.
If, on the other hand, you were to pass "Marco"
and 42
as the second and third arguments with the same format string, then the fmt.Printf
function would replace the two placeholders and print the following string:
my name is Marco and I am 42 years of age
You can see how elegant the use of the format string was, allowing us to work with values of different types before converting them all to one long concatenated string and printing it out to the console.
Looking at Some Verbs Used in the Printf Function
As we have discussed above, the verbs used in the format string specify the type of the value that will be printed by the fmt.Printf
function.
There are a great number of verbs available. However, it’s not necessary to memorize them all, because only a few will be used most often. Below is a list of the mostly commonly used verbs:
Verb | Type |
---|---|
"%s" | string |
"%t" | boolean |
"%d" | integer, represented in the base-10 (decimal) number system |
"%b" | integer, represented in the base-2 (binary) number system |
"%o" | integer, represented in the base-8 (octal) number system |
"%x" | integer, represented in the base-16 (hexadecimal) number system |
"%f" | floating-point number |
"%v" | any |
It is particularly worth remembering that the 'b'
character in "%b"
stands for the word “binary”, and not “boolean”.
On the other hand, the 't'
in "%t"
stands for “true or false”.
If in doubt about which verb to use, just remember that you can always use "%v"
, and it will print out a sensible default representation of any value.
Setting Precision in the Printf Function
In current versions of the Go programming language, floating-point numbers will be printed with a default precision of six. In other words, six digits will follow the decimal point, even if that means lots of unnecessary zeros are shown.
However, it is possible to print a floating-point number with a set degree of precision. For example, the code below prints the value of the mathematical constant pi to four decimal places, automatically rounding up the number as necessary:
package main
import "fmt"
func main() {
const pi = 3.14159265
fmt.Printf("%.4f\n", pi)
}
You can see that placing the ".4"
between the percentage symbol and the verb is what told the function to print the number only to four decimal places.
Therefore, if we had placed ".2"
between the percentage symbol and the verb, it’s easy to conclude that the number would have been printed with a precision of two decimal places instead.
Setting Width in the Printf Function
By default, fmt.Printf
will use only as much space as is necessary to print a value.
However, there are times when you might want to align values in columns, ensuring that each takes up a minimum amount of space.
To demonstrate that, I’ve created a somewhat real-world example below, involving the presentation of results for a class of students based on their performance in exams:
package main
import "fmt"
type SchoolGrade struct {
Name string
Scores []int
}
func (grade SchoolGrade) AverageScore() float64 {
var sum float64
for _, score := range grade.Scores {
sum += float64(score)
}
return sum / float64(len(grade.Scores))
}
func main() {
grades := []SchoolGrade{
{Name: "Xiaoming", Scores: []int{86, 78, 84, 87}},
{Name: "Jonathan", Scores: []int{56, 68, 66, 82}},
{Name: "Petunia", Scores: []int{91, 88, 96, 93}},
{Name: "Anil", Scores: []int{85, 80, 64, 89}},
{Name: "Roderick", Scores: []int{74, 60, 78, 70}},
}
fmt.Println(" NAME SCORE")
fmt.Println(" ========== ==========")
for _, grade := range grades {
fmt.Printf(
" %10s %10.2f\n",
grade.Name,
grade.AverageScore(),
)
}
}
The SchoolGrade
struct contains some basic information about each student in the class, just a name and a collection of the student’s examination results.
There is also a simple method called AverageScores
which adds up all of a student’s exam results and divides the sum by the number of exams, in order to return an score that represents the average performance across all exams.
Within the main
function, we have some dummy data for an imaginary set of students.
The interesting part, however, comes when we iterate through the data, printing a homemade table with two columns, one for each student’s name and the other for each student’s average exam result.
Running the program should produce the following output:
NAME SCORE
========== ==========
Xiaoming 83.75
Jonathan 68.00
Petunia 92.00
Anil 79.50
Roderick 70.50
If we look at the first placeholder in the format string that we pass to the fmt.Printf
function within the for
loop, we can see that by placing the number 10
between the percentage symbol and the 's'
, we stipulated that we wanted to print a string with a minimum width of ten characters, using spaces to pad as necessary. This is what aligns our columns so neatly.
The second placeholder stipulates both a width of ten and a precision of two, so the floating-point number that represents a student’s average score is always printed to two decimal places, and those decimal places are included when the function works out how much is required padding.
Since, in the example above, each of the average scores requires two digits followed by a decimal point and two more digits, each of the numbers is padded with five space characters, in order to bring the total width up to ten.
Looking at Some Flags Used in the Printf Function
Flags are special characters that modify verbs in some way.
(If you wanted to be clever and continue the analogy with the English language, you could think of them as adverbs, since adverbs are words that modify verbs in English grammar — and maybe that would have been a better name for them. It would also have reduced the risk of the confusion with command-line flags, which are something completely different.)
Each flag is placed after the '%'
character and before the verb in the format string.
Below is a table listing four flags that the fmt.Printf
function can make use of:
Flag | Purpose |
---|---|
"#" | adds a prefix (for example, "0x" before hexadecimal strings) |
"+" | always prints a positive or negative sign before numbers |
"-" | aligns the value towards the left when adhering to a width, which must also be specified |
"0" | pads numbers with zeroes, instead of spaces, when adhering to a specified width |
Using the Printf Function With Flags
The following example shows a very simple example of flags in action:
package main
import "fmt"
func main() {
number := 42
fmt.Printf("The number is %+08d\n", number)
}
If we read the placeholder in the format string from left to right, starting at the percentage symbol, then we can scan our eye towards the final 'd'
character, which is our verb, and see that we are expecting an integer to be printed in a base-10 representation.
In between the percentage symbol and the 'd'
character are the flags and the width specifier.
The character '+'
tells us that we always want to see a plus or minus printed, depending on whether the number is positive or negative — so since our number is positive, we’ll see a plus.
The number zero ensures that any padding will be done with zeroes, rather than spaces, and the number eight tells us that we want the width of the printed number to be at least eight-characters long, in this case including the plus sign.
So running the program will print the following output to the console:
The number is +0000042
Concluding Thoughts About Print Functions
We have now looked at the four most useful functions for printing to the console in Go.
When we just want to print a simple string to the console, we should generally use the fmt.Println
function, since it’s good practice always to emit a newline character after any other text.
However, when we want to handle the presentation of a number of values of differing types before printing them out to the console, we should rely on the fmt.Printf
function, which can be a much more complicated thing to work with. However, it is also a real workhorse of a function that allows us to arrange the presentation of our output precisely, once we have acquired a good understanding of the syntax used in its format string.