Golang Series — Empty Struct
Empty struct? What is it? When we use and what the advantages of using an empty struct?

Hi everyone! At this time I want to share some experience, thought, or opinions about technology-related to the software engineering field. However, my article could be a light topic and just come in short. I want to share my experience and opinion about using empty struct in Golang.
In the past, I usually found conditions that are made me use key only without value, for example having a unique key on a slice. Back then, I always use a map with bool key. Here is the way if I want to get unique int:
func UniqueInt(slice []int) []int {
mapInt := make(map[int]bool)
for _, v := range slice {
mapInt[v] = true
}
output := []int{}
for v := range mapInt{
output= append(output, v)
}
return output
}
However, sometimes I see some implementation using empty struct struct{}
. Why we should use this and what the advantages?
Starting from the size, empty struct
have the lowest size compared to others. Here the example code for getting the size:
package mainimport (
"fmt"
"unsafe"
)
func main() {
var s struct{}
var i interface{}
var b bool
fmt.Println(unsafe.Sizeof(s), unsafe.Sizeof(i), unsafe.Sizeof(b))
}
The output:
0 16 1
As we can see, empty struct has 0
. And bool
that I always use has 1
value.
Next, about memory usage. I’ve found some pieces of code (the references link can be found on the References part) and try to modify them to get the result from empty struct
and bool
as well. See the code below:
package main
import (
"fmt"
"runtime"
)
func main() {
// Below is an example of using our PrintMemUsage() function
// Print our starting memory usage (should be around 0mb)
fmt.Println("Start")
PrintMemUsage()
fmt.Println("")
structContainer := make(map[int]struct{}, 1000000)
for i := 0; i < 1000000; i++ {
structContainer[i] = struct{}{}
}
fmt.Println("With 1kk empty struct{}")
PrintMemUsage()
fmt.Println("")
nilContainer := make(map[int]interface{}, 1000000)
for i := 0; i < 1000000; i++ {
nilContainer[i] = nil
}
fmt.Println("With 1kk nil interface{}")
PrintMemUsage()
fmt.Println("")
boolContainer := make(map[int]bool, 1000000)
for i := 0; i < 1000000; i++ {
boolContainer[i] = true
}
fmt.Println("With 1kk true bool")
PrintMemUsage()
fmt.Println("")
}
// PrintMemUsage outputs the current, total and OS memory being used. As well as the number
// of garage collection cycles completed.
func PrintMemUsage() {
var m runtime.MemStats
runtime.ReadMemStats(&m)
// For info on each, see: https://golang.org/pkg/runtime/#MemStats
fmt.Printf("Alloc = %v KiB", bToMb(m.Alloc))
fmt.Printf("\tTotalAlloc = %v KiB", bToMb(m.TotalAlloc))
fmt.Printf("\tSys = %v KiB", bToMb(m.Sys))
fmt.Printf("\tNumGC = %v\n", m.NumGC)
}
func bToMb(b uint64) uint64 {
return b / 1024
}
When we run the code above, we will get this result:
Start
Alloc = 84 KiB TotalAlloc = 84 KiB Sys = 69714 KiB NumGC = 0With 1kk struct{}
Alloc = 21986 KiB TotalAlloc = 21999 KiB Sys = 71440 KiB NumGC = 1With 1kk nil interface{}
Alloc = 56649 KiB TotalAlloc = 78576 KiB Sys = 139154 KiB NumGC = 2With 1kk bool
Alloc = 80738 KiB TotalAlloc = 102665 KiB Sys = 139154 KiB NumGC = 2
From the data above, we can say empty struct
has the lowest memory usage due to Sys value. From the doc, Sys is the total bytes of memory obtained from the OS.
Then we can modify the code to find unique int like this:
func UniqueInt(slice []int) []int {
mapInt := make(map[int]struct{})
for _, v := range slice {
mapInt[v] = struct{}{}
}
output := []int{}
for v := range mapInt{
output= append(output, v)
}
return output
}
Thank you for reading my article, hopefully, it would be useful for you! See you at the next topic!