- 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.
spewis a common lib to print go structs recursive. We can callspewlib, and print its output.- Same way it that we can use
jsonto marshal structs and print its result, if the fields we need are export fields, becausejsondonnt marshal unexport fields. - Also we can implement
Stringmethod for struct, so when print them as a pointer, Go will call itsStringmethod 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,jsoniteror some other lib. - print each fields by custom.
- reuse
fmtlib 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.