SoFunction
Updated on 2025-03-05

Detailed explanation of Go language empty structure

Preface

During the development process of using the Go language, we inevitably define structures, but the structures we define all have fields, and basically no empty structures that do not contain fields. You might ask the other way around, what is the use of an empty structure without fields? Then let’s study the empty structure in this article!

Note: This article is based on go 1.14.4 analysis

What is an empty structure

We say that a structure that does not contain any fields is called an empty structure. An empty structure can be defined in the following ways:

Native definition

var a struct{}

Type alias

type empty struct{}
var e empty

Features

The address is the same

We define two non-empty structures and empty structure variables respectively, and then take the address to print them. We find that the addresses of the empty structure variables are the same:

// Define a non-empty structuretype User struct {
    name string
}

func main() {
 
  // The variable addresses of two non-empty structures are different  var user1 User
    var user2 User
    ("%p \n", &user1) // 0xc000318670
    ("%p \n", &user2) // 0xc000318680
  
  // Define two empty structures with the same address    var first struct{}
    var second struct{}
    ("%p \n", &first)    // 0x1ca15f0 
    ("%p \n", &second)   // 0x1ca15f0 
}

We know that variable transfers in Go are all value transfers. The variable addresses before and after passing parameters should be different. Let’s try it again by passing parameters:

// Non-empty structuretype NonEmptyUser struct {
    name string
}

// Empty structuretype EmptyUser struct{}

// Print the parameter address of non-empty structurefunc testNonEmptyUser(user NonEmptyUser) {
    ("%p \n", &user)
}

// Print empty structure parameter addressfunc testEmptyUser(user EmptyUser) {
    ("%p \n", &user)
}


func main() {
  
    // The variable addresses of two non-empty structures are different    var user1 NonEmptyUser
    ("%p \n", &user1) // 0xc0001986c0
    testNonEmptyUser(user1)            // 0xc0001986d0

  
    // The addresses of two empty structure variables are the same    var user2 EmptyUser
    ("%p \n", &user2) // 0x1ca25f0
    testEmptyUser(user2)                // 0x1ca25f0
  
}

It was found that for non-empty structures, the addresses before and after the parameter transmission are different, but for empty structure variables, the addresses before and after are the same.

Size is 0

In Go, we can use : to calculate the number of bytes occupied by a variable, so let’s take a few examples:

type EmptyUser struct{}

func main() {
    var i int
    var s string
    var m []string
    var u EmptyUser
  
    ((i)) // 8
    ((s)) // 16
    ((m)) // 24
    ((u)) // 0
}

You can see that the memory space occupied by an empty structure is 0, and the space occupied by a combination of an empty structure is also 0:

// Combination of empty structurestype EmptyUser struct {
    name struct{}
    age  struct{}
}

func main() {
    
    var u EmptyUser

    ((u)) // 0
}

Principle Exploration

Why are the addresses of empty structures the same and the size is 0? Let's take a look at the source code (go/src/runtime/):

// base address for all 0-byte allocations
var zerobase uintptr

// When creating a new object, call mallocgc to allocate memoryfunc newobject(typ *_type)  {
    return mallocgc(, typ, true)
}

func mallocgc(size uintptr, typ *_type, needzero bool)  {
    if gcphase == _GCmarktermination {
        throw("mallocgc called with gcphase == _GCmarktermination")
    }

    if size == 0 {
        return (&zerobase)
    }
    ......
}

From the source code, we can see that when creating a new object, we need to call () for memory allocation and further call the mallocgc method. In this method, if the size==0 of the type is determined, the address of zerobase is returned regularly. zerobase is a uintptr global variable that takes up 8 bytes.

So what we can be sure is that in Go language, all memory allocations for size==0 use the same address &zerobase, so all empty structure addresses we see at the beginning are the same.

Use scenarios

If an empty structure does not contain any data, then its application scenario should not care about the value content and just treat it as a placeholder. In this scenario, since it does not occupy memory space, using an empty structure can not only save space, but also provide semantic support.

Set (Set)

Students who have used Java should have used the Set type. Set is a collection that holds no duplicate elements, but Go does not provide native Set type. However, we know that the Map structure stores the key-value type, and the key does not allow duplication, so we can use Map to implement Set. The key stores the required data, and just give a fixed value to the value. So what value is good for value? At this time, our empty structure can appear, without occupying space, and can also complete the placeholding operation, which is perfect. Let's see how to achieve it.

// Define a Set collection that saves the string typetype Set map[string]struct{}

// Add an elementfunc (s Set) Add(key string) {
    s[key] = struct{}{}
}

// Remove an elementfunc (s Set) Remove(key string) {
    delete(s, key)
}

// Whether to include an elementfunc (s Set) Contains(key string) bool {
    _, ok := s[key]
    return ok
}

// Initializationfunc NewSet() Set {
    s := make(Set)
    return s
}
// Test usefunc main() {
    set := NewSet()
    ("hello")
    ("world")
    (("hello"))

    ("hello")
    (("hello"))
}

Signal transmission in channel

An empty structure and channel are a classic combination. Sometimes we just need a signal to control the running logic of the program and don’t care about its content.

In the following example, we define two channels to receive signals completed by two tasks. When a signal completed by the task is received, the corresponding action will be triggered.

func doTask1(ch chan struct{}) {
    ()
    ("do task1")
    ch <- struct{}{}
}

func doTask2(ch chan struct{}) {
    ( * 2)
    ("do task2")
    ch <- struct{}{}
}

func main() {

    ch1 := make(chan struct{})
    ch2 := make(chan struct{})
    go doTask1(ch1)
    go doTask2(ch2)

    for {
        select {
        case <-ch1:
            ("task1 done")
        case <-ch2:
            ("task2 done")
        case <-( * 5):
            ("after 5 seconds")
            return
        }
    }
}

Summarize

In this article, we have learned the following content:

  • An empty structure is a special structure that does not contain any elements
  • The size of the empty structure is 0
  • The empty structures are all the same
  • Since the empty structure does not occupy space, it is suitable for realizing the Set structure, transmitting signals in the channel, etc. from the perspective of saving memory.

This is the end of this article about the detailed explanation of Go language empty structure. For more related contents of Go language empty structure, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!