Go1.26 新特性:new(expr) 終于來了,創建指針簡單了!
今天給大家分享一個 Go1.26 即將帶來的實用新特性—— new(expr) 表達式支持。
圖片
相信寫過 Go 的同學都有過這樣的經歷:創建一個結構體指針很簡單,但要創建一個基本類型的指針就得多寫好幾行代碼。還是挺煩人的。
這個特性能解決日常開發中的小痛點。
背景
在 Go 中,如果我們想創建一個指向結構體的指針,可以這樣寫:
type Person struct { name string }
p := &Person{name: "alice"}非常簡潔優雅。但如果我們想創建一個指向基本類型值的指針呢?
就得這么寫:
n := 42
p := &n或者用 new 函數:
p := new(int)
*p = 42看起來就很不協調對吧?為什么創建復合類型的指針可以一步到位,而基本類型就得多寫一個臨時變量或者兩行代碼?
這個問題其實在社區里討論了很久。早在 2014 年的 issue #9097[1] 就有人提出過,但當時被駁回了。

為什么不能直接寫 &3?
可能有同學會問:為什么不直接支持 p := &3 這種寫法呢?
問題就出在類型系統上。在 Go 中,數字字面量 3 是一個無類型常量(untyped constant),它本身沒有確定的類型。只有在賦值或者運算時,編譯器才會根據上下文推斷出具體類型。
如果允許 &3 這種寫法,編譯器就不知道該分配什么類型的內存空間 —— 是 int、int64 還是 float64?
這會帶來類型歧義,所以這條路走不通。
新提案:擴展 new 函數
Go 團隊最終采納的方案是:讓 new 函數不僅可以接受類型參數,還可以接受表達式參數。
快速例子:
p1 := new(int, 3)
p2 := new(rune, 10)
p3 := new(Weekday, Tuesday)
p4 := new(Name, "unspecified")
... and so on語法規則
新的 new 函數行為如下:
- 如果參數
expr是一個類型為 T 的表達式,或者是一個默認類型為 T 的無類型常量表達式,那么new(expr)會分配一個類型為 T 的變量,將其初始化為expr的值,并返回其地址(類型為*T) - 如果參數是類型 T,那么
new(T)會分配一個初始化為零值的 T 類型變量(這是原有行為)
簡單來說,就是 new 現在既能接受類型,也能接受值了。
代碼示例
基本類型指針
以前我們得這么寫:
// Go 1.25
n := 42
p1 := &n
fmt.Println(*p1) // 42
s := "go"
p2 := &s
fmt.Println(*p2) // go現在可以直接:
// Go 1.26
p1 := new(42)
fmt.Println(*p1) // 42
p2 := new("go")
fmt.Println(*p2) // go復合類型指針
對于切片這類復合類型也同樣適用:
// Go 1.25
s := []int{11, 12, 13}
p1 := &s
fmt.Println(*p1) // [11 12 13]
type Person struct{ name string }
p2 := &Person{name: "alice"}
fmt.Println(*p2) // {alice}新寫法:
// Go 1.26
p1 := new([]int{11, 12, 13})
fmt.Println(*p1) // [11 12 13]
type Person struct{ name string }
p2 := new(Person{name: "alice"})
fmt.Println(*p2) // {alice}函數返回值指針
這個場景以前特別麻煩:
// Go 1.25
f := func() string { return "go" }
v := f()
p := &v
fmt.Println(*p) // go現在一行搞定:
// Go 1.26
f := func() string { return "go" }
p := new(f())
fmt.Println(*p) // go需要注意的是,傳入 nil 仍然是不允許的:
p := new(nil) // 編譯錯誤實現原理
其實這個特性的實現思路很簡單。當你寫:
p := new(42)編譯器會將其轉換為類似這樣的操作:
var _tmp = 42
p := &_tmp或者等價于:
p := new(int)
*p = 42也就是說,new(expr) 本質上是為非可尋址的表達式顯式分配存儲空間,然后返回其地址。
這樣就統一了創建指針的方式,不管是基本類型還是復合類型,都可以用類似的語法。
社區討論中的另一個方案
其實在 proposal 討論過程中,Rob Pike 還提出了另一個有趣的方案:讓類型轉換變得可尋址。
比如可以這樣寫:
p1 := &int(3)
p2 := &rune(10)
p3 := &string("hello")這個方案的邏輯是:類型轉換必然會創建新的存儲空間(因為要改變類型),所以讓它可尋址是合理的。
不過最終 Go 團隊選擇了擴展 new 的方案,可能是考慮到:
new函數的語義本身就是"分配并初始化",擴展它更自然。- 避免引入新的語法歧義。
- 保持
&運算符語義的一致性。
實際應用場景
這個特性雖然看起來簡單,但在實際開發中還是挺有用的。比如:
1. 配置選項
很多第三方庫的配置結構體中會有指針類型的字段,用來區分"未設置"和"設置為零值":
type Config struct {
Timeout *int
MaxRetry *int
}
// Go 1.25
timeout := 30
config := Config{
Timeout: &timeout,
MaxRetry: new(int), // 只能用 new(int) 表示 0
}
// 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. 內聯指針創建
在函數調用時需要傳指針參數:
// Go 1.25
func process(val *string) {}
s := "data"
process(&s)
// Go 1.26
process(new("data"))總結
new(expr) 這個特性解決了 Go 中創建基本類型指針時的不便,讓代碼更簡潔一致。雖然改動不大,但確實提升了開發體驗。
這個特性將會在 Go1.26 版本正式發布,由 Alan Donovan、Ian Lance Taylor、Rob Pike 等核心團隊成員推動完成。
對于我們 Go 開發者來說,以后寫代碼可以少定義臨時變量了,代碼也會相對干凈些。

花了 11 年啊。絕了,真的服了。































