- TLDR
- Raise of Question
- Design of fmt
- Ways to Print a Struct
- Performance of Ways
- Simplify Generating String Method
- Note
- Summary
TLDR
Printing Go structs with pointer fields using the fmt library often displays the pointer’s address instead of the struct’s content. This blog explores why Go adopts this approach, how to customize struct printing effectively, and introduces stringergen, a tool for automating String method generation to simplify struct customization and improve code maintainability.
Raise of Question
When you print a Go struct that contains a field which is a pointer to another struct using the fmt
library, you might notice that the field is printed as an address rather than the actual content of the pointed-to struct.
We will dive into why Go desinn fmt
library this way and how to proper print such kind structs.
Design of fmt
Lets have a quick example of the question.
|
|
In this example, OuterStruct
contains a pointer Inner
to InnerStruct
. When printing outer
using %+v
, Go prints the address of Inner
, not the contents of InnerStruct
. The output would look like:
|
|
Through the source code of fmt, we can find the reason why it only print address for fields within a struct. If the arg is in top level, Go will get its reference, otherwise Go will not get its reference to avoid loop. Aussming a situtaion, where a pointer field in struct contain the pointer to the struct itself, if Go dereference the pointer field, there will be a infinite loop.
|
|
Ways to Print a Struct
Since fmt
refuse to print pointer field, while it is a very common situation in Go. What if we want to print it, and it is actually very normal case such as in debuging and logging.
I have come up with some ways to do it.
spew
is a common lib to print go structs recursive. We can callspew
lib, and print its output.- Same way it that we can use
json
to marshal structs and print its result, if the fields we need are export fields, becausejson
donnt marshal unexport fields. - Also we can implement
String
method for struct, so when print them as a pointer, Go will call itsString
method directly. For example:
|
|
In the example, we implement String
method for subComplicated
and subSubComplicated
structs, so when we print a complicated
struct, Go will call the String
method when trying to print pointer field in complicated
struct.
As for how to implement String
method, there are few options:
- call
spew
,json
,jsoniter
or some other lib. - print each fields by custom.
- reuse
fmt
lib itself.
How to resue fmt
lib, here is a example:
|
|
It may seems wield, but it is quite useful and simple. When fmt
print the pointer to its struct, it will call this String
method, and it will reuse fmt
lib and return the result of struct but not pointer to struct.
Besides it, I come up with a another similar way:
|
|
In String
method, we cast the input type into another type, and resue fmt
to print another type. It can do too.
Performance of Ways
We have talk about many ways to print a struct recusively, but which one is the best when it comes to performance. I did a benchmark and the result is like below. The source code and fully result can be checked Here.
Way | CPU Time Consumed |
---|---|
json | 100 |
jsoniter | 103 |
cat string with stringbuilder | 189 |
cat string with sprintf | 196 |
reuse fmt with dereference | 265 |
Spew | 298 |
reuse fmt with type case | 310 |
Simplify Generating String Method
From the benchmark, we find that json
have best performance. But still it is inconvenient to use to print struct. Because we have to either marshal struct every time and then print its result, or define String
method for all struct we gonna use and call json
in that method.
In order to simplify the process, I come up with a idea to use a tool to auto generate String
method for all structs defined in project. With the tool, we just need to run a command, and then String
method for all structs will be generated. We dont have to worry how to print a pointer field any more.
I have already implement the tool stringergen. We can generate String
method for all structs like this, feel free to test it and explore more usage:
|
|
Note
Be cautious that we haven’t solved the problem of cyclic reference, and if it exist, we may have error or infinite loop cauing stackoverflow.
Summary
In this blog, we explored the intricacies of printing Go structs with pointer fields using the fmt
library. We delved into Go’s design philosophy behind struct printing, examining how Go prioritizes memory safety and performance by defaulting to printing addresses for pointer fields. We examined practical solutions for customizing struct printing, such as implementing the String
method for structs or using serialization libraries like json
or jsoniter
for recursive printing. Additionally, we benchmarked these approaches to understand their performance implications. Finally, we introduced stringergen
, a tool designed to automate the generation of String
methods for Go structs, simplifying the process of struct customization and enhancing code maintainability.
By understanding these concepts and tools, Go developers can effectively manage struct printing, balancing readability, performance, and ease of use in their projects.