【golang】用 new 来初始化基本类型会怎么样

7

这是一个非常好的问题,而且正好能把 “类型零值语义 + 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 结果的统一对比表

类型

new(T) 之后内部状态

直接使用结果

是否可补救

风险等级

[]T

slice header 全零

越界 panic

✅ make 后不可见补救

⚠️ 中

map[K]V

*map 指向 nil hmap

赋值 panic

✅ make 后可用

❗ 高

chan T

*chan 指向 nil hchan

永久阻塞 / close panic

✅ make 后可用

❗❗ 极高


五、从 runtime 角度的本质总结(非常重要)

new(T)

runtime 实际执行:

mallocgc(sizeof(T), nil, true)

只做两件事:

  1. 分配一块 T 大小的内存

  2. 按位清 0

永远不会调用:

  • makeslice

  • makemap

  • makechan

因此:

类型是否“可用”

是否依赖 runtime 额外初始化

slice

✅ 需要数组分配

map

✅ 需要 hmap + buckets

chan

✅ 需要 hchan + buf + waitq

而这些 仅由 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 才是这三类类型的“唯一正确构造器”