Hello, I’m fried fish.

There is a special type in Go language, which is often asked or not understood by friends who are new to Go.

This is the use of empty struct in Go, which is often seen as:

ch := make(chan struct{})
Copy the code

The use of structures is uniform, and no other types are used. Highly common, it is not an accidental phenomenon, there must be some reason behind it.

In today’s article, we will take you to understand why it is used in this way.

Happily start sucking the fish together.

Why use

Basically, you want to save space. But, again, the question is, why can’t you do it in a different type?

This refers to the concept of “width” in the Go language, which describes the number of bytes of storage that an instance of a type takes up.

Width is an attribute of a type. Each value in the Go language has a type, the width of the value is defined by its type, and is always a multiple of 8 bits.

In the Go language, we use the safe.Sizeof method to retrieve:

// Sizeof takes an expression x of any type and returns the size in bytes
// of a hypothetical variable v as if v was declared via var v = x.
// The size does not include any memory possibly referenced by x.
// For instance, if x is a slice, Sizeof returns the size of the slice
// descriptor, not the size of the memory referenced by the slice.
// The return value of Sizeof is a Go constant.
func Sizeof(x ArbitraryType) uintptr

Copy the code

This method gets the width of the value, which automatically tells you what the width of its type is.

Let’s take a look at several common type widths in Go:

func main() {
 var a int
 var b string
 var c bool
 var d [3]int32
 var e []string
 var f map[string]bool

 fmt.Println(
  unsafe.Sizeof(a),
  unsafe.Sizeof(b),
  unsafe.Sizeof(c),
  unsafe.Sizeof(d),
  unsafe.Sizeof(e),
  unsafe.Sizeof(f),
 )
}
Copy the code

Output result:

8, 16, 1, 12, 24, 8Copy the code

You can see that we’ve listed several types, just to make a statement that we’re not doing anything, but still taking up some width.

What if our scenario is just a placeholder, and the overhead in the system is just wasted?

The particularity of empty structure

One of the reasons empty structures appear so frequently in all kinds of systems is the need for a placeholder. As it happens, the width of the Go empty structure is special.

As follows:

func main() {
 var s struct{}
 fmt.Println(unsafe.Sizeof(s))
}
Copy the code

Output result:

0
Copy the code

The width of the empty structure is a straightforward 0, even with deformation:

type S struct {
 A struct{}
 B struct{}
}

func main() {
 var s S
 fmt.Println(unsafe.Sizeof(s))
}
Copy the code

Its final output is also 0, which perfectly meets people’s basic demand for placeholders, which is to occupy the pit and satisfy the basic input and output.

But then the question arises, why do empty structures get this special treatment, and not other types?

This is an optimization that the Go compiler does when allocating memory

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

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
 ...
 if size == 0 {
  return unsafe.Pointer(&zerobase)
 }
}
Copy the code

When size is found to be 0, a reference to the variable Zerobase is returned directly, which is the base address of all 0 bytes and does not occupy any width.

Hence the widespread use of empty structures, which Go developers have taken advantage of as placeholders.

Usage scenarios

Now that we understand why empty structures are used as placeholders, we can take a closer look at the actual usage scenarios.

It is mainly divided into three parts:

  • Implement method receivers.

  • Implement collection types.

  • Implement empty channels.

Implementation method receiver

In a business scenario, we need to group the methods together to represent a “grouping” for subsequent expansion and maintenance.

But if we use:

type T string

func (s *T) Call()
Copy the code

And it seems a little unfriendly because, as a string type, it takes up a certain amount of space.

In this case, we will use empty structs, which will make it easier to add public fields and so on to the type in the future. As follows:

Type T struct{} func (s *T) Call() {FMT.Println()} func main() {var s T s.call ()}Copy the code

In this scenario, it is the most appropriate to use empty structure from multi-dimensional consideration, which is easy to expand, saves space and is the most structured.

In addition, you will find that you already do this subconsciously in your daily development, and you can see it as an alternative case of design patterns and daily life.

Implementing collection types

In the standard library of Go language, there is no related implementation of Set (Set), so it is usually convenient to use map in the code.

There’s a problem, though, with collection types, you only need keys, you don’t need values.

Here’s how the empty structure works:

type Set map[string]struct{} func (s Set) Append(k string) { s[k] = struct{}{} } func (s Set) Remove(k string) { delete(s, k) } func (s Set) Exist(k string) bool { _, Set.append (" fish ") set.append (" fish ") set.append (" fish ") set.append (" fish ") set.append (" fish ") set.append (" fish ") set.remove (" fish ") FMT.Println(set.exist (" fry "))}Copy the code

Empty structures as placeholders do not add unnecessary memory overhead, it is very convenient to solve.

Implement empty channel

In Go channel usage scenarios, it is common to encounter notification channels, which do not need to send any data, but are used to coordinate the running of Goroutine, to flow various states, or to control concurrency.

As follows:

Func main() {ch := make(chan struct{}) go func() {time.sleep (1 * time.second) close(ch)}() FMT.Println(" ) <-ch FMT.Println(" Fried fish!" )}Copy the code

Output result:

The brain seems to go into... The fish!Copy the code

The program will first output “brain like… “After that, sleep for some time and then output” fried fish!” To achieve the effect of discontinuous channel control.

Since this channel uses an empty structure, it incurs no additional memory overhead.

conclusion

In today’s article, we introduced several common types of widths in Go, and analyzed them based on the question “empty structure” at the beginning.

Finally, the three most common codes in the industry are analyzed to enter the real scene. Have you ever wondered like this before?

You are welcome to leave your comments in the comments section 🙂

If you have any questions, welcome feedback and communication in the comments section. The best relationship is mutual achievement. Your praise is the biggest motivation for the creation of fried fish, thank you for your support.

This article is constantly updated. You can read it on wechat by searching “Brain into fried fish”. GitHub github.com/eddycjy/blo… Star has been included, welcome to urge more.