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