struct{} 真的一字节不占,unsafe.Sizeof 返回 0;但必须用 struct{}{} 初始化值,不可省略大括号;适用于 map[string]struct{} 和 chan struct{} 等零内存场景,禁用于 JSON 序列化、反射判断及需唯一标识的场合。

空结构体 struct{} 不是语法糖,也不是“看起来空”,它真的一字节都不占——unsafe.Sizeof(struct{}{}) 返回 0。但直接当类型用、漏写字面量大括号、或误以为能 JSON 序列化,都会立刻报错或静默失效。
map[string]struct{} 写法和常见编译错误
用 struct{} 当 map value 是最常见场景,但新手常卡在赋值语法上。
- 错误写法:
m["key"] = struct{}—— 这是类型,不是值,编译报cannot use struct{} as value - 正确写法:
m["key"] = struct{}{}—— 末尾的{}是复合字面量,表示该类型的空值 - 别用
var zero struct{}再赋值,多一次变量声明,没收益 -
map[string]bool虽然写起来省事(m["key"] = true),但每个 value 固定占 1 字节;而map[string]struct{}的 value 占 0 字节,百万级 key 下能省接近 1MB 内存
chan struct{} 发送与接收的语义陷阱
chan struct{} 的核心价值是“信号即事件”,但容易在缓冲区和关闭逻辑上出错。
- 发送必须用
ch <- struct{}{},不能省略{},否则编译失败 - 接收可写成
<-ch(忽略值)或<-ch(显式接收),但_, ok := <-ch会编译报错 ——struct{}没字段,无法解构 -
make(chan struct{}, 0)是非缓冲通道(默认),make(chan struct{}, 1)才能缓存一个信号;虽然底层内存开销几乎一样(value 不占空间),但语义不同:前者发信号必阻塞直到有人收,后者允许“发完就走” - 关闭后接收会立即返回零值(即
struct{}{})且ok为 false,这是退出循环的标准模式
struct{} 作为方法接收器时的隐含限制
定义 type Logger struct{} 并实现方法看似轻量,但实际有边界条件。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 接收器可以是值类型(
func (l Logger) Info())或指针类型(func (l *Logger) Info()),两者都合法,因为struct{}复制成本为 0 - 但若该类型要满足某个接口,而接口方法签名含指针接收器,则必须用
*Logger实现,否则不匹配 - 所有
struct{}实例地址可能相同(指向runtime.zerobase),所以&Logger{}和&Logger{}可能是同一地址 —— 别拿它做唯一性标识或 map key - 不能嵌入
struct{}后还指望“继承”行为:比如type T struct{ struct{} },T{}和struct{}{}是不同类型,不能互转
哪些地方坚决不能用 struct{}
它不是万能零开销占位符,强行套用反而引入 bug。
- JSON 序列化时输出
null,反序列化时如果目标字段是struct{},会静默跳过(不报错,但字段保持零值) - 反射中
reflect.ValueOf(struct{}{}).NumField()返回 0,但很多 ORM 或配置解析库依赖字段数判断是否为空对象,结果直接忽略整个结构 - 数组或切片元素用
[]struct{}虽省内存,但len([]struct{})始终是 0,cap也无意义 —— 它只适合当信号容器,不适合存数据 - 需要运行时动态添加字段、或依赖结构体名/包路径做类型判断的场景(如某些泛型约束、mock 工具),
struct{}会丢失上下文
真正难的不是记住 struct{}{} 怎么写,而是每次想用它替代 bool 或 int 时,先问一句:这里“零内存”带来的收益,是否盖过了语义模糊、序列化断裂、反射失效的风险?

















