
在 Go 语言中,创建复合类型(如结构体)的指针非常方便,可以直接使用 &S{a: 3} 语法。但创建简单类型(如 int、string)的指针却需要多步操作:先声明变量,再取地址。这种不一致性长期困扰着开发者。Go 1.26 将通过扩展 new() 内置函数来解决这个问题,使简单类型和复合类型的指针创建方式更加统一。
目前的情况:
在 Go 语言中,创建指针的方式存在不一致性:
& 操作符创建指针存在的问题:
// 复合类型 - 非常方便
p1 := &User{Name: "Alice"} // 直接创建结构体指针
p2 := &[3]int{1, 2, 3} // 直接创建数组指针
// 简单类型 - 需要多步操作
a := 3
p3 := &a // 必须先声明变量
// 或者使用 new() 函数
p4 := new(int) // 只能创建零值指针
*p4 = 3 // 然后赋值
这种不一致性导致:
复合类型 (Composite Types)
Go 中的复合类型包括:
这些类型可以直接使用字面量语法创建,并用 & 操作符获取指针。
简单类型 (Simple Types)
简单类型包括:
这些类型不能直接取地址,必须通过变量或 new() 函数创建指针。
new() 内置函数
当前 Go 的 new() 函数:
TT 零值的指针 *Tnew(int) 返回指向 int 零值(0)的指针提案扩展
Go 1.26 提案将扩展 new() 函数:
new(T, expression)T 的指针,将 expression 的值赋给该指针指向的内存// 当前写法 (Go 1.25 及之前)
a := 3
p1 := &a // 需要先声明变量
// 新写法 (Go 1.26)
p2 := new(int, 3) // 直接创建指向 3 的指针
fmt.Println(*p2) // 输出: 3
// 同样适用于字符串
s := new(string, "hello") // 直接创建指向 "hello" 的指针
fmt.Println(*s) // 输出: hello
代码讲解:新语法 new(int, 3) 创建了一个 int 类型的指针,并立即将值 3 赋给它。这相当于:
temp := 3
p := &temp
但编译器会优化这一过程,直接在栈上分配内存。
func getValue() int {
return42
}
// 当前写法 - 无法直接获取
func getValuePointer1() *int {
v := getValue()
return &v // 需要中间变量
}
// 新写法 (Go 1.26)
func getValuePointer2() *int {
returnnew(int, getValue()) // 直接获取返回值的指针
}
代码讲解:新语法允许直接获取表达式的指针,这对于获取函数返回值、复杂计算结果的指针非常有用。
// 假设有一个 API 响应结构体
type Response struct {
ID int `json:"id"`
Name string`json:"name"`
Score *int `json:"score,omitempty"`
}
// 当前写法
func processResponseOld(resp *Response) {
score := 100
if resp.Name != "" {
resp.Score = &score // 需要中间变量
}
}
// 新写法 (Go 1.26)
func processResponseNew(resp *Response) {
if resp.Name != "" {
resp.Score = new(int, 100) // 直接创建指针
}
}
代码讲解:在处理 JSON、gRPC 等协议时,经常需要为可选字段创建指针。新语法使代码更加简洁。
实现原理:
编译器处理
new(T, expr) 时,编译器会生成类似以下代码:var temp T = expr
return &temp
内存分配优化
v := expr; &v 相同类型检查
expr 的类型可以赋值给类型 T限制和边界情况:
类型必须匹配
// 编译错误
p := new(int, "hello") // string 不能赋值给 int
不能获取栈上临时变量的地址
// 新语法解决的问题
p := new(int, someFunction() + 5) // ✅ 允许
与现有 new() 语法共存
p1 := new(int) // ✅ 旧语法仍然有效
p2 := new(int, 42) // ✅ 新语法
不能用于复合类型
// 复合类型仍然使用 & 操作符
p := &User{Name: "Alice"} // ✅ 推荐方式
p := new(User, User{Name: "Alice"}) // ❌ 不推荐
特性 | Go 1.26 | Go 1.25 | Java | Python |
|---|---|---|---|---|
基本类型指针创建 | new(int, 42) | 需中间变量 | 无指针概念 | 无指针概念 |
复合类型指针创建 | &User{} | &User{} | 对象引用 | 对象引用 |
类型安全性 | 编译时检查 | 编译时检查 | 编译时检查 | 运行时检查 |
内存分配位置 | 栈/堆自动优化 | 栈/堆自动优化 | 堆 | 堆 |
学习曲线 | 低 | 中 | 中 | 低 |
对比分析:
✅ 推荐做法:
对于基本类型,使用新的 new() 语法
p := new(int, 42)
s := new(string, "hello")
对于复合类型,继续使用 & 操作符
p := &User{Name: "Alice"}
利用编译器的逃逸分析
// 函数内使用,不会逃逸到堆
func process() {
p := new(int, calculate())
// 使用 p
}
⚠️ 注意事项:
类型匹配必须精确
// 可能的类型转换问题
var x int32 = 42
p := new(int, x) // ⚠️ int32 到 int 的隐式转换
注意 nil 指针问题
// 新语法创建的指针永远不为 nil
p := new(int, 42)
if p == nil { // 永远不会执行
}
性能考虑
// 在热路径中,避免频繁创建指针
for i := 0; i < 1000000; i++ {
p := new(int, i) // ⚠️ 可能影响性能
}
❌ 避免做法:
不要对复合类型使用 new(expr)
// 不推荐
p := new(User, User{Name: "Alice"})
// 推荐使用
p := &User{Name: "Alice"}
不要过度使用指针
// 不必要的指针
p := new(int, 42)
fmt.Println(*p) // 直接使用 42 更好
我的看法:
这个提案是一个非常实用和务实的改进。它解决了 Go 语言中一个长期存在的不一致性问题,使简单类型和复合类型的处理方式更加统一。
主要优点:
潜在问题:
new() 函数有两种不同的用法可能会让初学者困惑适用场景:
未来展望:
这个提案将在 Go 1.26 中实现。建议开发者:
参考链接:
引用链接
[1]
Github: https://github.com/golang/go/issues/45624