心情可以分享
心事如何共鸣
Go 语言即将在 1.26 版本 中引入一项备受期待的语法增强:new
内置函数将支持传入任意表达式,而不仅限于类型标识符 。这一变化看似微小,却能显著简化代码、提升性能,并解决长期困扰 Go 开发者的“字面量取地址”问题。
- 背景:为什么需要这个特性?
在 Go 中,不能对字面量(如 123
、"hello"
)或函数返回值直接取地址 。例如:
type Config struct {
Timeout *int
}
// ❌ 编译错误:cannot take address of 30
cfg := Config{
Timeout: &30,
}
为绕过此限制,开发者通常会编写辅助函数:
funcintPtr(v int) *int {
return &v
}
cfg := Config{
Timeout: intPtr(30), // ✅ 可行
}
这类函数在大型项目(如 K8s、gRPC、JSON 序列化库)中极为常见。但它们存在两个问题:
代码冗余 :每个基本类型都需要一个辅助函数;
潜在性能开销 :由于逃逸分析保守,辅助函数中的变量通常会被分配到堆上,导致额外内存分配。
Go 团队意识到,这一模式如此普遍,值得在语言层面提供原生支持。
- 新特性:
new
支持表达式
从 Go 1.26 开始,new
不再仅接受类型,还可以接受任意表达式 :
p := new(42) // *int,指向值为 42 的整数
s := new("hello") // *string,指向 "hello"
now := new(time.Now()) // *time.Time
语义说明
new(expr)
的行为等价于:
var tmp T = expr // T 是 expr 的类型
p := &tmp
Go 编译器会自动创建一个临时变量,将表达式结果复制进去,并返回其地址。
- 实际使用示例
示例 1:结构体字段赋值
type User struct {
ID *int
Name *string
}
funcmain() {
user := User{
ID: new(1001),
Name: new("Alice"),
}
fmt.Printf("ID: %d, Name: %s\n", *user.ID, *user.Name)
}
无需繁琐再写 intPtr(1001)
或 strPtr("Alice")
。
示例 2:与函数返回值结合
funcgetDefaultPort()int {
return8080
}
funcmain() {
config := struct {
Port *int
}{
Port: new(getDefaultPort()),
}
fmt.Println(*config.Port) // 输出: 8080
}
示例 3:复杂表达式
base := 100
offset := 25
ptr := new(base + offset) // *int = 125
fmt.Println(*ptr) // 125
示例 4:避免大对象内存泄漏
考虑以下场景:
type BigData struct {
// 假设有 10KB 数据
Data [10240]byte
Flag int
}
funcprocess() {
big := BigData{Flag: 42}
// ❌ 危险:持有 big.Flag 的指针会导致整个 big 无法释放
// small.Flag = &big.Flag
// ✅ 安全:new 复制值,不引用原对象
small := struct{ Flag *int }{
Flag: new(big.Flag),
}
// 此时 big 可被 GC 回收,即使 small 仍存活
}
使用 new(big.Flag)
会复制 Flag
的值 ,而非持有原结构体字段的指针,从而避免“意外持有大对象”的内存泄漏问题。
- 性能优势
官方基准测试显示,new(expr)
比辅助函数快 约 30% ,且无额外堆分配 。
性能对比代码
funcintPtr(v int) *int { return &v }
funcBenchmarkHelper(b *testing.B) {
for i := 0; i < b.N; i++ {
p := intPtr(123)
\_ = *p
}
}
funcBenchmarkNew(b *testing.B) {
for i := 0; i < b.N; i++ {
p := new(123)
\_ = *p
}
}
结果(Go 1.26-devel)
BenchmarkHelper-8 100000000 10.2 ns/op 8 B/op 1 allocs/op
BenchmarkNew-8 100000000 7.1 ns/op 0 B/op 0 allocs/op
✅
new(123)
零堆分配 ,且更快,因为编译器可将临时变量优化到栈上。
- 与现有
new(T)
的兼容性
原有的 new(Type)
用法完全保留:
p := new(int) // 分配零值 int 的指针
q := new(42) // 分配值为 42 的 int 指针
两者共存,互不冲突。
- 注意事项
- •
new(expr)
中的expr
会被 求值一次 ,其结果被复制 - • 表达式类型必须是
可寻址的非接口类型
(与原
new
限制一致) - • 该特性目前处于 Go 1.26 开发阶段 ,最终行为以正式发布为准
结语
Go 1.26 对 new
的扩展,是“小改动,大收益”的典范。它:
- • 消除了重复的辅助函数;
- • 提升了代码简洁性与可读性;
- • 还带来了极大的性能提升。
对于频繁使用指针字段(如 JSON、gRPC、配置结构)的项目,这一特性将带来显著的开发体验改善。
📌 建议 :待 Go 1.26 正式发布后,可逐步将项目中的
*T
辅助函数替换为new(expr)
。