乐趣区

关于go:Go-121版本新特性前瞻

本文首发自「慕课网」,想理解更多 IT 干货内容,程序员圈内热闻,欢送关注 ” 慕课网 ”!

作者:tonybai | 慕课网讲师


Go 1.21 版本正在热火朝天地开发当中,依照 Go 外围团队的一年两次的公布节奏来算,Go 1.21 版本预计将在 2023 年 8 月公布(Go 1.20 版本是在 2023 年 2 月份公布的)。

本文将和大家一起看看 Go 1.21 都会带来哪些新个性。不过因为目前为时尚早,上面列出的有些变动最终不肯定能进入到 Go 1.21 的最终版本中,所以切记所有变更要以最终 Go 1.21 版本公布时为准。

在细数变动之前,咱们先来看看 Go 语言的以后状态。

1. Go 语言以后状态

Go 语言的 2022 年初排名为 12 位,同时 TIOBE 官网编辑也提到:“在新兴编程语言中,Go 是惟一一个可能在将来冲入前十的后端编程语言”。Go 语言的倒退仿佛应验了这一预测,在往年的 3 月份,Go 就再次进入编程语言排行榜前十:

一个月后的四月初,TIOBE 排行榜上,Go 稳住了第 10 名的位次:

在国内,在鹅厂前不久公布的《2022 年腾讯研发大数据报告》中,
在国内,继 Go 在 2021 年从 C ++ 手中躲过红旗首次登顶鹅厂最热门编程语言之后,在鹅厂前不久公布的《2022 年腾讯研发大数据报告》中,Go 蝉联鹅厂最热门编程语言,持续夯实在国内头部互联网公司内的劣势位置:

Go 于 2009 年开源,在经验多年的宣传和宣扬后,Go 目前进入了安稳倒退的阶段。疫情完结后,原先线上举办或勾销的国内外的 Go 技术大会当初陆续又都开始复原了,置信这会让更多开发人员接触到 Go。像 Go 这样的能在世界各地继续多年举办技术大会的语言真是不多了。
接下来,咱们就来聚焦到 Go 1.21 版本,开掘一下这个版本都有哪些新个性。

2. 语言变动

目前 Go 1.21 版本里程碑中波及语言变动的有大概 2 项,咱们来看看。

2.1 减少 clear 预约义函数

Go 1.21 减少了一个 clear 预约义函数用来做切片和 map 的 clear 操作,其原型如下:

// $GOROOT/src/builtin.go

// The clear built-in function clears maps and slices.
// For maps, clear deletes all entries, resulting in an empty map.
// For slices, clear sets all elements up to the length of the slice
// to the zero value of the respective element type. If the argument
// type is a type parameter, the type parameter's type set must
// contain only map or slice types, and clear performs the operation
// implied by the type argument.
func clear[T ~[]Type | ~map[Type]Type1](t T)

clear 是针对 map 和 slice 的操作函数,它的语义是什么呢?咱们通过一个例子来看一下:

package main

import "fmt"

func main() {var sl = []int{1, 2, 3, 4, 5, 6}
    fmt.Printf("before clear, sl=%v, len(sl)=%d, cap(sl)=%d\n", sl, len(sl), cap(sl))
    clear(sl)
    fmt.Printf("after clear, sl=%v, len(sl)=%d, cap(sl)=%d\n", sl, len(sl), cap(sl))

    var m = map[string]int{
        "tony": 13,
        "tom":  14,
        "amy":  15,
    }
    fmt.Printf("before clear, m=%v, len(m)=%d\n", m, len(m))
    clear(m)
    fmt.Printf("after clear, m=%v, len(m)=%d\n", m, len(m))
}

运行该程序:

before clear, sl=[1 2 3 4 5 6], len(sl)=6, cap(sl)=6
after clear, sl=[0 0 0 0 0 0], len(sl)=6, cap(sl)=6
before clear, m=map[amy:15 tom:14 tony:13], len(m)=3
after clear, m=map[], len(m)=0

咱们看到:

  • 针对 slice,clear 放弃 slice 的长度和容量,但将所有 slice 内已存在的元素 (len 个) 都置为元素类型的零值;
  • 针对 map,clear 则是清空所有 map 的键值对,clear 后,咱们将失去一个 empty map。

2.2 扭转 panic(nil)语义

应用 defer+recover 捕捉 panic 是 Go 语言惟一解决 panic 的办法,其典型模式如下:

package main

import "fmt"

func foo() {defer func() {if err := recover(); err != nil {fmt.Printf("panicked: %v\n", err)
            return
        }
        fmt.Println("it's ok")
    }()

    panic("some error")
}

func main() {foo()
}

运行下面程序会输入:
panicked: some error

例子中咱们向 panic 传入了示意 panic 起因的字符串,panic 的参数是一个 interface{}类型,能够传入任意值,当然也能够传入 nil。

比方下面例子,当咱们给 foo 函数的 panic 调用传入 nil 时,咱们将失去上面后果:
it's ok

这可能会给开发者带去纳闷:明明是触发了 panic,但函数却依照失常逻辑解决!2018 年,前 Go 外围团队成员 bradfitz 就提出了一个 issue:spec: guarantee non-nil return value from recover,提出当开发者调用 panic(nil)时,recover 应该返回某种 runtime error,而不是 nil。这个 issue 在往年被纳入了 Go 1.21 版本,当初该 issue 的实现曾经被 merge 到了骨干。

新的实现在 src/runtime/panic.go 中定义了一个名为 PanicNilError 的新 Error:

// $GOROOT/src/runtime/panic.go

// A PanicNilError happens when code calls panic(nil).
//
// Before Go 1.21, programs that called panic(nil) observed recover returning nil.
// Starting in Go 1.21, programs that call panic(nil) observe recover returning a *PanicNilError.
// Programs can change back to the old behavior by setting GODEBUG=panicnil=1.
type PanicNilError struct {
    // This field makes PanicNilError structurally different from
    // any other struct in this package, and the _ makes it different
    // from any struct in other packages too.
    // This avoids any accidental conversions being possible
    // between this struct and some other struct sharing the same fields,
    // like happened in go.dev/issue/56603.
    _ [0]*PanicNilError
}

func (*PanicNilError) Error() string { return "panic called with nil argument"}
func (*PanicNilError) RuntimeError() {}

Go 编译器会将 panic(nil)替换为 panic(new(runtime.PanicNilError)),这样咱们用 Go 1.21 版本运行下面的程序,咱们就会失去上面后果了:

panicked: panic called with nil argument

如果你的遗留代码中调用了 panic(nil)(注:显然这不是一种很 idiomatic 的作法),降级到 Go 1.21 版本后你就要小心了。如果你想保留原先的 panic(nil)行为,能够用 GODEBUG=panicnil=1。

有童鞋可能会质疑这违反了 Go1 兼容性承诺,但实际上 Go1 兼容性标准保留了对语言标准中不统一或谬误的订正势力,即使这种订正会导致遗留代码呈现与原先不统一的行为。

3. 编译器与工具链

每个 Go 版本中,编译器和工具链的改变都不少,咱们挑重点看一下:

3.1 一些 OS 的最小反对版本的更新

Go 1.21 开始,go installer 反对最小 macOS 版本更新为 10.15,而最小 Windows 版本为 Windows 10。

3.2 低版本的 go 编译器将回绝编译高版本的 go module

从 Go 1.21 版本开始,低版本的 go 编译器将回绝编译高版本的 go module(go.mod 中 go version 标识最低版本),这也是 Russ Cox 策动的 Go 扩大的向前兼容性提案的一部分。此外,Go 扩大向前兼容性提案感觉比较复杂,可能不会全副在 Go 1.21 版本落地。

3.3 反对 WASI

Go 从 1.11 版本就开始反对将 Go 源码编译为 wasm 二进制文件,并在反对 wasm 的浏览器环境中运行。

不过 WebAssembly 绝不仅仅被设计为仅限于在 Web 浏览器中运行,外围的 WebAssembly 语言是独立于其周围环境的,WebAssembly 齐全能够通过 API 与内部世界互动。在 Web 上,它天然应用浏览器提供的现有 Web API。然而,在浏览器之外,之前还没有一套规范的 API 能够让 WebAssembly 程序应用。这使得创立真正可移植的非 Web WebAssembly 程序变得艰难。
WebAssembly System Interface(WASI)是一个填补这一空白的倡导,它有一套洁净的 API,能够由多个引擎在多个平台上实现,并且不依赖于浏览器的性能(只管它们依然能够在浏览器中运行)。

Go 1.21 将减少对 WASI 的反对,初期先反对 WASI Preview1 版本,之后会反对 WASI Preview2 版本,直至最终 WASI API 版本公布!目前咱们能够应用 GOOS=wasip1 GOARCH=wasm 将 Go 源码编译为反对 WASI 的 wasm 程序,上面是一个例子:

// main.go
package main            
                        
func main() {println("hello")    
}                     

下载最新 go dev 版本后(go install http://golang.org/dl/gotip@latest),能够执行上面命令将 main.go 编译为 wasm 程序:

$ GOARCH=wasm GOOS=wasip1 gotip build -o main.wasm main.go

开源的 wasm 运行时有很多,wazero 是目前比拟火的且应用纯 Go 实现的 wasm 运行时程序,装置 wazero 后,能够用来执行下面编译进去的 main.wasm:

$curl https://wazero.io/install.sh
$wazero run main.wasm
hello  

3.4 Go 1.21 可能推出纯动态工具链,不再依赖 glibc

应用纯 Go 实现的 net resolver,原先 DNS 的问题也将被解决,这样 Go 团队在构建工具链的时候应用 CGO_ENABLED= 0 构建出动态工具链,没有动态链接库的依赖。

3.5 go test - c 反对为多个包同时构建测试二进制程序

Go 1.21 版本之前,go test - c 仅反对将单个包的测试代码编译为测试二进制程序,Go 1.21 版本则容许咱们同时为多个包构建测试二进制程序。
上面是官网给出的例子:

$ go test -c -o /tmp ./pkg1 ./pkg2 ./pkg2
$ ls /tmp
pkg1.test pkg2.test pkg3.test

3.6 减少 $GOROOT/go.env

明天应用 go env
- w 命令批改的默认环境变量会写入:filepath.Join(os.UserConfigDir(),
“go/env”)。在 Mac 上,这个门路是 *$HOME/Library/Application
Support/go/env;在 Linux 上,这个门路是$HOME/.config/go/env*。

Go 1.21 将减少一个全局档次上的 go.env,放在 $GOROOT 上面,目前默认的 go.env 为:

// $GOROOT/go.env

# This file contains the initial defaults for go command configuration.
# Values set by 'go env -w' and written to the user's go/env file override these.
# The environment overrides everything else.

# Use the Go module mirror and checksum database by default.
# See https://proxy.golang.org for details.
GOPROXY=https://proxy.golang.org,direct
GOSUMDB=sum.golang.org

咱们依然能够通过 go env- w 命令批改 user 级的 env 文件来笼罩上述配置,当然最高优先级的是 OS 用户环境变量,如果在 OS 用户环境变量文件 (比方.bash_profile、.bashrc) 中设置了 Go 的环境变量值,比方 GOPROXY 等,那么以 OS 用户环境变量为优先。

4. 规范库

咱们接下来再来看看变更最多的一部分:规范库,咱们将对次要变更项作简要介绍。

4.1 slices 和 maps 进入规范库

Go 1.18 版本泛型落地公布前的最初一刻,Rob Pike 叫停了 slices、maps 等泛型包的入库,起初这两个包先搁置在 golang.org/x/exp 下作为试验包。随着 Go 泛型日益成熟以及 Go 团队对泛型应用教训的增多,Go 团队终于决定将 golang.org/x/exp/slices 和 golang.org/x/exp/maps 在 Go 1.21 版本中将挪入规范库。

4.2 log/slog 退出规范库

log/slog 是 Go 官方版结构化日志包,大抵与 uber 的 zap 包相当。在我之前的一篇文章《slog:Go 官方版结构化日志包》有对 slog 的详尽阐明,大家能够移步到那篇文章看看。不过 slog 的 proposal 仍旧很多,后续 slog 可能会有继续改良和变更,与那篇文章中的内容可能会有一些差别。

4.3 sync 包减少 OnceFunc、OnceValue 和 OnceValues

在 sync.Once 的根底上,这个 issue 减少了三个与 Once 相干的 ” 语法糖 ”API,用在一些对 Once 有需要的最常见的场景中。

4.4 减少 testing.Testing 函数

Go 1.21 为 testing 包减少了 func Testing() bool 函数,该函数能够用来报告以后程序是否是 go test 创立的测试程序。应用 Testing 函数,咱们能够确保一些无需在单测阶段执行的函数不被执行。比方上面例子来自这个 issue:

// file/that/should/not/be/used/from/testing.go

func prodEnvironmentData() *Environment {if testing.Testing() {log.Fatal("Using production data in unit tests")
    }
    ....
}

4.5 一些变更点

  • context: 减少为 deadline 或 timeout context 设置 cancel 起因的 API – https://github.com/golang/go/issues/56661
  • unicode 降级到 15.0 版本 – <https://github.com/golang/go/issues/55079

5. 参考资料

  • Go 1.21 milestone – https://github.com/golang/go/milestone/279

欢送关注「慕课网」帐号,咱们会始终保持内容原创,提供 IT 圈优质内容,分享干货常识,大家一起独特成长吧!

本文原创公布于慕课网,转载请注明出处,谢谢合作

退出移动版