Understanding and Using Empty Structs (struct{}) in Go
This article explains the zero‑size nature of Go's empty struct, demonstrates how its memory address behaves, shows its impact on struct alignment, and presents multiple practical patterns such as set implementation, large arrays, signal channels, no‑op methods, interface stubs, and the noCopy identifier.
In Go, the empty struct struct{} is a special type that contains no fields and occupies zero bytes of memory. Although it seems useless, it has many practical applications.
Zero‑size and address behavior
Using unsafe.Sizeof we can verify that any empty struct instance has size 0. Multiple empty structs may share the same address, but this is not guaranteed; the Go spec states that two distinct zero‑size variables may have the same address . Examples show both identical and different addresses depending on compiler flags and escape analysis.
Effect on memory alignment
When an empty struct is used as a field inside another struct, its position can affect the overall size because of alignment rules. Placing the empty field at the end of a struct may increase the outer struct’s size, while placing it at the beginning does not.
Typical use cases
Set implementation : By combining a map with struct{} values, we can create a memory‑efficient set. type Set map[string]struct{} func (s Set) Add(e string) { s[e] = struct{}{} } func (s Set) Remove(e string) { delete(s, e) } func (s Set) Contains(e string) bool { _, ok := s[e]; return ok } func (s Set) Size() int { return len(s) }
Large capacity arrays : An array of struct{} has size 0 regardless of length, allowing the creation of massive logical arrays without memory cost. var a [1000000]string var b [1000000]struct{} fmt.Printf("array a size: %d\n", unsafe.Sizeof(a)) // 16000000 fmt.Printf("array b size: %d\n", unsafe.Sizeof(b)) // 0
Large capacity slices : Slices always store only a header (pointer, length, capacity). Whether the element type is struct{} or another type, the slice header size remains the same. var a = make([]string, 1000000) var b = make([]struct{}, 1000000) fmt.Printf("slice a size: %d\n", unsafe.Sizeof(a)) // 24 fmt.Printf("slice b size: %d\n", unsafe.Sizeof(b)) // 24
Signal channels : A channel of struct{} can be used as a lightweight notification mechanism because sending a zero‑size value incurs no data copy. done := make(chan struct{}) go func() { time.Sleep(1 * time.Second) fmt.Println("goroutine done") done <- struct{}{} }() fmt.Println("waiting...") <-done fmt.Println("main exit")
No‑op method receivers : Methods that do not need a receiver can use struct{} to avoid allocating memory. type NoOp struct{} func (n NoOp) Perform() { fmt.Println("Performing no operation.") }
Interface stubs : Empty structs implement interfaces without adding overhead, useful for discarding output (e.g., io.Discard ) or creating fake objects in tests. type discard struct{} func (discard) Write(p []byte) (int, error) { return len(p), nil } var Discard io.Writer = discard{}
noCopy identifier : Embedding an empty noCopy struct in a type prevents accidental copying; go vet can detect such misuse. type noCopy struct{} func (*noCopy) Lock() {} func (*noCopy) Unlock() {} type A struct { noCopy; a string }
Conclusion
The empty struct struct{} in Go, despite having no fields and zero size, provides a versatile tool for memory‑efficient data structures, synchronization primitives, placeholder implementations, and compile‑time safety checks. Understanding its behavior helps write clearer, more performant Go code.
Go Programming World
Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.