相信不少 Go 开发者都有过类似的体验:创建一个结构体指针时语法简洁优雅,但需要创建一个基本类型(如 int, string)的指针时,却不得不额外引入一个临时变量。这种不一致性给日常编码带来了一些不便。
好在,即将发布的 Go 1.26 版本引入了一个实用的新特性——扩展的 new(expr) 表达式,旨在解决这一痛点。

背景:创建指针的两种体验
在 Go 语言中,创建复合类型(如结构体、切片)的指针非常直接:
type Person struct { name string }
p := &Person{name: "alice"}
然而,当我们需要一个指向基本类型值的指针时,写法就变得冗余:
n := 42
p := &n
// 或者使用 new 函数
p := new(int)
*p = 42
为什么会有这种差异?这个问题在社区中由来已久,早在 2014 年就有人在 issue #9097 中提出,但当时并未被采纳。

为何不支持 &3 这种写法?
一个自然的想法是:为什么不直接允许 p := &3 这样的语法呢?关键在于 Go 的类型系统。数字字面量 3 是一个无类型常量,在编译期没有确定的类型(可以是 int, int64, float64 等)。如果允许 &3,编译器将无法确定应为该值分配何种类型的内存空间,从而产生类型歧义。因此,直接取字面量地址的路径行不通。
新方案:扩展 new 函数
Go 团队最终采纳的方案是扩展内置函数 new 的语义,使其不仅能接受类型参数,也能接受表达式参数。
语法规则:
- 当参数
expr 是一个类型为 T 的表达式,或是一个默认类型为 T 的无类型常量表达式时,new(expr) 会分配一个 T 类型的变量,将其初始化为 expr 的值,并返回其地址(类型为 *T)。
- 当参数是类型
T 时,new(T) 的行为保持不变,即分配一个零值的 T 类型变量并返回其地址。
简单来说,new 现在既可用于分配零值,也可用于“分配并初始化”一个指定值的指针。
代码示例对比
1. 基本类型指针
// Go 1.25 及之前
n := 42
p1 := &n
s := "go"
p2 := &s
// Go 1.26 新写法
p1 := new(42) // *int
p2 := new("go") // *string
2. 复合类型指针
// Go 1.25 及之前
s := []int{11, 12, 13}
p1 := &s
p2 := &Person{name: "alice"}
// Go 1.26 新写法
p1 := new([]int{11, 12, 13})
p2 := new(Person{name: "alice"})
3. 函数返回值指针(此前需多步操作)
// Go 1.25 及之前
f := func() string { return "go" }
v := f()
p := &v
// Go 1.26 新写法
f := func() string { return "go" }
p := new(f()) // 一行搞定
需要注意的是,new(nil) 仍然是非法操作,会导致编译错误。
实现原理与社区讨论
这个特性的实现思路直观:new(expr) 在编译时会被转换为一个临时变量的创建与取址操作。例如 p := new(42) 大致等价于:
var _tmp = 42
p := &_tmp
在提案讨论过程中,曾有过另一个有趣的方案:让类型转换表达式可寻址(如 &int(3))。其逻辑是类型转换本身就会创建新值。但最终,Go 团队认为扩展 new 函数的语义更为自然,它本就意味着“分配内存”,且能保持 & 运算符语义的一致性,避免引入新的语法歧义。对于关心底层实现和编译原理的开发者来说,这是一个权衡后的优雅设计。
实际应用场景
这一特性虽小,但能在多个场景下提升代码的简洁性。
1. 配置选项
许多库使用指针字段来区分“未设置”和“设置为零值”。
type Config struct {
Timeout *int
MaxRetry *int
}
// Go 1.25
timeout := 30
config := Config{
Timeout: &timeout,
MaxRetry: new(int), // 零值指针
}
// Go 1.26
config := Config{
Timeout: new(30),
MaxRetry: new(0),
}
2. 测试代码
在编写单元测试时,构造测试用例的数据更加清晰。
testCases := []struct {
input *int
expected string
}{
{new(42), "success"},
{new(0), "zero"},
{nil, "nil"},
}
3. 内联指针创建
在函数调用中直接传递新创建的指针,减少临时变量,这对于追求简洁的后端 架构代码很有帮助。
// 之前
func process(val *string) {}
s := "data"
process(&s)
// Go 1.26
process(new("data"))
总结
new(expr) 是 Go 1.26 带来的一项小而实用的语法增强,它统一了创建各类值指针的语法,消除了基本类型与复合类型在此操作上的不一致性,让代码更加简洁。虽然这个特性的讨论历时颇久,但它最终落地,体现了 Go 语言在保持简洁哲学的同时,持续优化开发者体验的努力。