1、空struct{}不占用内存空间

Go语言中,使用unsafe.Sizeof()可以计算一个数据类型实例所占用的字节数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	fmt.Printf("struct{} %d\n", unsafe.Sizeof(struct {}{})) // struct{} 0
}

通过上面的结果可以看到,Go中空结构体struct{}是不占用内存空间。

因为空结构体不占用内存空间,所以在各种场景下被广泛使用,一是能够节省内存,二是空结构体本身没有任何实际意义,也不需要任何值,仅作为占位符,能够达到代码即注释的效果。

2、实现集合

Go语言中没有标准的Set的实现,一般用map来替代。因为对于集合来说,只需要map的key,而不需要值,所以将值设置为空结构体,能够节省内存。

以下是一个简单的Set实现,仅做参考:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
type Set map[string]struct{}

func (s Set) Add(k string) {
	s[k] = struct{}{}
}

func (s Set) Delete(k string) {
	if _, ok := s[k]; ok {
		delete(s, k)
	}
}

func (s Set) Has(k string) bool {
	_, ok := s[k]
	return ok
}

3、作为信号量

有时候我们需要通知子协程执行任务或者结束任务,仅作为信号量在channel中传递。因为不需要在changnel中传递任何数据,所以struct{}作为信号量就非常合适。

举个例子,主协程main通知任务协程task开始工作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package main

import (
	"fmt"
)

func doTask(ch chan struct{}) {
	<-ch
	fmt.Printf("start do task\n")
}

func main() {
	sig := make(chan struct{})
	go doTask(sig)
	
	sig <- struct{}{}
	
	close(sig)
}

4、仅包含方法的结构体

有些场景下,我们只需要实现某些方法,而不需要任何字段。那这种情况下用空结构体struct{}作为方法的接受者很合适。

举个例子:

1
2
3
4
5
type foo struct{}

func (f *foo) Do() {
	// do some thing
}