Skip to content
Ben Sooraj

The empty interface and the empty struct in golang

golang1 min read

An interface is a collection or set of method declarations. A data type implements or satisfies an interface if it at least defines the methods declared by the interface.

Empty interface

An empty interface interface{} has zero methods. So, in essence, any data type implements or satisfies an empty interface. Let's take the following example:

1type Container []interface{}
2
3func (c *Container) Put(elem interface{}) {
4 *c = append(*c, elem)
5 switch elem.(type) {
6 case int:
7 fmt.Println("Put ", elem, " of type int")
8 case string:
9 fmt.Println("Put ", elem, " of type string")
10 default:
11 fmt.Println("Put ", elem, " of type unknown")
12 }
13}
14
15func (c *Container) Pop() interface{} {
16 elem := (*c)[0]
17 *c = (*c)[1:]
18 return elem
19}
20
21func main() {
22 allContainer := &Container{}
23
24 allContainer.Put("Hello")
25 allContainer.Put(213)
26 allContainer.Put(123.321)
27
28 fmt.Printf("allContainer: %+v\n\n", allContainer)
29}

Output:

1$ go run main.go
2Put Hello of type string
3Put 213 of type int
4Put 123.321 of type unknown
5allContainer: &[Hello 213 123.321]

In the above example, the data type Container is a slice of empty interfaces with two methods defined on it, Put() and Pop(). It loosely resembles a First In, Last Out stack.

The Put() method takes an interface{} as the input argument and appends it to the slice of interface{}s. This means that it can accept a string, an integer, a float or any other simple and composite data type as its input argument. You can access the underlying type of elem, an interface{}, using elem.(type).

This approach does have its disadvantages though. One being that you should implement type assertions, checks or type specific logics to avoid any surprises during runtime.

Empty struct

To quote the Golang spec:

A struct is a sequence of named elements, called fields, each of which has a name and a type.

Let's take an example

1// A struct with 5 fields.
2struct {
3 x, y int
4 u float32
5 A *[]int
6 F func()
7}

An empty struct is a struct data type with zero fields:

1// Named type
2type EmptyStruct struct{}
3
4// Variable declaration
5var es struct{}
6
7// Or use it directly!
8struct{}{}

The size or width of a struct is defined as the sum of its constituent types. An empty struct, since it has no fields within it, has a size or width of zero. Zero bytes!

Just like regular structs you can define methods on empty structs as well. Sort of like a zero sized container for methods:

1type EmptyStruct struct{}
2
3func (es *EmptyStruct) WhoAmI() {
4 fmt.Println("I am an empty struct!")
5}

Empty structs find its best use case in channel signalling. Many a mere mortals such as me have used booleans or integers to notify an event over a channel.

The following example prints the current time at every 500 milliseconds and times out after 3 seconds,

1func printFor3Seconds(doneChannel chan struct{}) {
2 ticker := time.NewTicker(500 * time.Millisecond)
3 timeout := time.After(3 * time.Second)
4
5 for {
6 select {
7 case t := <-ticker.C:
8 fmt.Printf("Tik tik: %v\n", t)
9 case <-timeout:
10 fmt.Printf("Timeout at: %v\n", time.Now())
11 doneChannel <- struct{}{}
12 return
13 }
14 }
15}
16
17func main() {
18 doneChannel := make(chan struct{}, 1)
19
20 go printFor3Seconds(doneChannel)
21
22 <-doneChannel
23}
24
25// Output
26// $ go run 1.go
27// Tik tik: 2020-09-16 08:25:36.750631 +0530 IST m=+0.501680080
28// Tik tik: 2020-09-16 08:25:37.250303 +0530 IST m=+1.001337269
29// Tik tik: 2020-09-16 08:25:37.750696 +0530 IST m=+1.501714888
30// Tik tik: 2020-09-16 08:25:38.253708 +0530 IST m=+2.004711715
31// Tik tik: 2020-09-16 08:25:38.752804 +0530 IST m=+2.503793180
32// Tik tik: 2020-09-16 08:25:39.251941 +0530 IST m=+3.002914584
33// Timeout at: 2020-09-16 08:25:39.251999 +0530 IST m=+3.002972846

Instead of doneChannel <- 0 or doneChannel <- true, I am using doneChannel <- struct{}{} for channel signalling. Using booleans or integers for channel signalling involves memory allocation, copying over the element etc.

We can use empty structs for simulating sets as well,

1intSet := make(map[int]struct{})
2empty := struct{}{}
3
4// We are setting empty as the value for 1, because the
5// value is meaningless
6intSet[1] = empty
7if _, ok := intSet[1]; ok {
8 fmt.Println("1 is in the set")
9}
10
11// 2 is not one of intSet's keys
12if _, ok := intSet[2]; !ok {
13 fmt.Println("2 is not in the set")
14}

Resources:

  1. The Go Programming Language Specification
  2. The empty struct - Dave Cheney

Note: This article is not an in-depth tutorial or treatment of Golang's syntax, semantics, design or implementation, but a journal of my learnings.