new 与 make:分配语义、逃逸与选型¶
30 秒版(开场)¶
传统
new(T)为 T 提供零值存储并返回*T;Go 1.26 又允许new(expr),返回指向该表达式值副本的指针。两者最终在栈、堆还是被优化掉仍由编译器决定。make仅用于 slice、map、channel,初始化其可用内部状态并返回 T 本身(非指针)。
3 分钟版(精讲深度)¶
- 是什么:两者都是内置函数,非
runtime.new的普通函数;编译器特殊处理。Go 1.26 以前new的操作数只能是类型;1.26 起也可以是表达式,用表达式结果初始化新变量。 - 为什么:slice/map/chan 的零值或仅是描述符,或代表不可用状态,需要初始化内部结构;
new只是取得一个指向零值 T 的指针,不承诺上堆。 - 怎么做:slice/map 用
make或var+append;*T零值用new(T)或更常见的&T{};Go 1.26 的可选标量字段可用new(valueExpr);channel 必须make才能用。
10 分钟版(原理 + 图示)¶
对比表
new(T) |
new(expr)(Go 1.26+) |
make(T, args) |
|
|---|---|---|---|
| 操作数 | 类型 | 表达式 | slice、map 或 chan 类型及参数 |
| 返回值 | *T |
指向表达式结果类型的指针 | T(已初始化) |
| 结果状态 | 指向零值 T | 指向表达式值的副本 | 已初始化的 slice/map/chan |
| nil 语义 | 返回的指针非 nil | 返回的指针非 nil | make 后值非 nil |
flowchart LR
newT["new(T)"] --> Storage[零值存储:栈/堆/优化消除]
newExpr["new(expr) Go 1.26+"] --> Initialized[表达式值副本:栈/堆/优化消除]
makeS["make([]T,n,c)"] --> Runtime[初始化 slice]
Runtime --> Header[ptr,len,cap]
lit["&T{}"] --> Storage
与 composite literal
&T{}与new(T)等价语义;习惯上 struct 用&Config{}可读更好。- Go 1.26 的
new(expr)类似先求值v := expr再取&v的结果语义,适合 JSON/Protobuf 中用指针表达“可选标量”;不要误说成new(T, value)。 []int{}vsmake([]int, 0):两者都是非 nil 空 slice;var s []int是 nil slice。以encoding/json默认行为为例,前者编码为[],nil slice 编码为null。
逃逸:new 结果若未逃逸,可能被优化到栈;提一句即可。
常见 follow-up
make(map[K]V)vsmake(map[K]V, hint):后者减扩容。make(chan T, 0)无缓冲 vs 省略 buffer 同义。
生产场景¶
- 配置加载:
cfg := &Config{}比new(Config)团队风格统一。 - 缓冲 channel:
make(chan Event, 1024)背压;忘记 make 的 nil chan 永久阻塞。 - 可观测:nil map 写 panic;nil slice
append合法。
排查与工具¶
| 场景 | 处理 |
|---|---|
| nil map panic | 改 make 或判 nil |
| nil chan 死锁 | 必须 make |
| JSON null vs [] | 区分 nil slice 与 make([]T,0) |
架构取舍¶
| 写法 | 适用 | 不适用 |
|---|---|---|
&T{} |
struct 指针 | 需要强调零值指针语义时 |
make(..., cap) |
已知规模 | 未知小量 |
var m map 延迟 make |
可选 map | 确定会用应直接 make |
深挖问答¶
- new 返回指针为何不是 nil? → 指向已分配零值。
- make slice len 与 cap? → len 可索引范围,cap 可扩上限。
- new([]int) 合法吗? → 合法但得到
*[]int指向 nil slice,少见。 - Go 1.26 的
new(42)返回什么? →*int,指向值为 42 的新变量;不是把 42 当容量,也不是新的构造器语法。 - 和 malloc 区别? → Go 受 GC 管理,类型安全,且分配位置由逃逸分析决定。
- 零值可用设计? → sync.Mutex、bytes.Buffer 等零值即用,无需 new。
反模式与事故¶
var ch chan T直接<-ch死锁。new(sync.Mutex)取址传参,多余;零值 Mutex 即可。- 以为
make返回指针——类型题经典坑。
代码示例¶
// slice:nil vs empty vs make
var a []int // nil, json "null"
b := []int{} // empty, json "[]"
c := make([]int, 0) // empty non-nil
d := make([]int, 0, 64) // 预 cap
// map 必须 make 再写
m := make(map[string]int, 128)
// chan
events := make(chan Event, 256)
// struct 指针:惯用写法
cfg := &Config{Timeout: 3 * time.Second}
// Go 1.26+:为可选标量直接创建已初始化指针
retries := new(3) // *int,值为 3