【golang】用 new 来初始化基本类型会怎么样
这是一个非常好的问题,而且正好能把 “类型零值语义 + runtime 初始化 + new/make 本质差异” 彻底打通。结论先行:
✅
new(slice)、new(map)、new(chan)都能编译通过❌ 但几乎都“不能直接用”,因为内部 runtime 结构根本没有被初始化
✅ 本质上:
new(T)只负责 分配 + 清零内存,make(T)还负责 初始化运行时内部结构
我们逐个拆开来看。
一、new([]T):得到的是 *slice,但底层数组是 nil
p := new([]int)
此时内存状态:
type slice struct {
Data *int // nil
Len int // 0
Cap int // 0
}
等价于:
var s []int
p == &s
1️⃣ 直接使用会发生什么?
(*p)[0] = 1
直接 panic:
panic: runtime error: index out of range
因为:
Data == nil
Len == 0
任何索引都越界
2️⃣ 但它可以被“补救”
p := new([]int)
*p = make([]int, 10) // ✅ 现在才真正初始化
(*p)[0] = 1 // ✅ 正常
结论:
✅
new([]T)只生成一个“空壳 slice header 指针”❌ 不会分配底层数组
❌ 不具备任何可用数据区
二、new(map[K]V):得到 *map,但 hmap 仍是 nil
p := new(map[string]int)
内存状态:
p != nil
*p == nil // map 还是 nil
等价于:
var m map[string]int
p == &m
1️⃣ 直接写入会 panic
(*p)["a"] = 1
panic:
panic: assignment to entry in nil map
因为:
hmap 尚未分配
buckets 尚未初始化
2️⃣ 正确用法(必须再 make 一次)
p := new(map[string]int)
*p = make(map[string]int, 100)
(*p)["a"] = 1 // ✅ 正常
结论:
✅
new(map)只创建了一个 指向 nil map 的指针❌
hmap结构依然不存在
❌ 任何写操作都会 panic
三、new(chan T):得到的是 *chan,但 hchan 仍是 nil
p := new(chan int)
内存状态:
p != nil
*p == nil // chan 仍是 nil
1️⃣ 发送 / 接收都会永久阻塞
go func() {
(*p) <- 1 // 永久阻塞
}()
<-(*p) // 永久阻塞
原因(runtime 逻辑):
if c == nil {
gopark(...)
}
2️⃣ close 直接 panic
close(*p)
panic:
panic: close of nil channel
3️⃣ 正确用法(仍然必须 make)
p := new(chan int)
*p = make(chan int, 10)
(*p) <- 1
fmt.Println(<-(*p)) // ✅ 正常
四、三类 new 结果的统一对比表
五、从 runtime 角度的本质总结(非常重要)
new(T)
runtime 实际执行:
mallocgc(sizeof(T), nil, true)
只做两件事:
分配一块 T 大小的内存
按位清 0
永远不会调用:
makeslicemakemapmakechan
因此:
而这些 仅由 make 完成。
六、为什么标准库中“几乎从不用 new(map/slice/chan)”?
因为:
m := new(map[K]V)
相比:
m := make(map[K]V)
✅ 多了一次解引用
✅ 更容易产生 nil map panic
✅ 更难被内联和逃逸分析优化
✅ 完全没有任何工程收益
所以 idiomatic Go 结论是:
✅ slice / map / chan:只用 make
❌ 不要用 new
✅
new只适合:
struct
array
基础类型(int、bool 等)
七、一个“总览级公式化结论”
你可以用这一句在脑子里“强制记忆”:
凡是 runtime 内部有“隐藏结构体”的类型,都不能只靠 new 初始化。
对应三类:
slice → array + header
map → hmap + buckets
chan → hchan + buf + waitq
这些隐藏结构,只存在于 make 的实现里。
八、实战中你最容易踩的两个坑
❌ 坑 1:函数参数是 *map
func Init(m *map[string]int) {
*m = make(map[string]int)
}
调用方如果是:
var m map[string]int
Init(&m)
✅ 正确
但如果你误写:
m := new(map[string]int)
Init(m)
逻辑会变得极不直观,可维护性很差。
❌ 坑 2:nil chan 导致 goroutine 全部“假死”
var ch chan int
go worker(ch)
worker 里:
ch <- job // 整个 worker 永久 park
这类 bug 在生产环境极难排查。
九、最终结论(一句话版)
✅
new(slice/map/chan):只分配“指针壳子”❌ 不会初始化:
slice 的底层数组
map 的哈希桶
chan 的环形队列
✅ 只有
make才是这三类类型的“唯一正确构造器”