Go 1.22 就要在龙年春节期间发布了。Go 1.22的新特性包括了新的 math/rand 包。这个包的目标是提供一个更好的伪随机数生成器,它的 API 也更加简单易用。本文将介绍这个新的包的特性。
Go 1.22 release notes[1] 正在编写之中,大家可以关注这个网页以便全面了解Go 1.22的变化,前几天有Gopher制作了一个交互式运行新特性代码的网页[2],也非常好,在reddit上关注度很高。今天这篇文章只关注于于math/rand/v2这个新的包。
为什么要新的math/rand包
其实大家对math/rand不是那么满意。
2017年,#20661[3] 中提到math/rand.Read和crypto/rand.Read相近,导致本来应该使crypto/rand.Read的地方使用了math/rand.Read,导致了安全问题。
2017年,#21835[4] 中 Rob Pike 提议在Go 2中使用PCG Source。
2018年,#26263[5] 中 Josh Bleecher Snyder 提议对math/rand进行彻底的重构。
2023年6月, Russ Cox基于先前的对math/rand的吐槽,以及和Rob Pike的讨论,建立了一个讨论(#60751[6]),准备新建一个包math/rand/v2,重新设计和实现一个新的伪随机数的库讨论也很热烈,最后实现了一个提案#61716[7],这个提案最直接的动机是清理 math/rand 并解决其中许多悬而未决的问题,特别是使用过时生成器、缓慢的算法,以及与 crypto/rand.Read 的不幸冲突。
由于go module的支持版本v2、v3、..., Go 1.22中将会有一个新的包math/rand/v2,这个包将会是一个新的包,而不是math/rand的升级版本。这个包的目标是提供一个更好的伪随机数生成器,它的 API 也更加简单易用,同时一些检查工具也能支持这个包,不会报错。
看样子,math/rand/v2将会是第一个在标准库中建立v2版本的包,如果大家能够接受,将来会有更多的包加入进来,比如sync/v2、encoding/json/v2等等。
提案的主要内容
math/rand/v2 API 以 math/rand 为起点,进行以下不兼容的更改:
1、 移除 Rand.Read 和顶层的 Read。假装伪随机生成器是任意长字节序列的良好来源几乎总是错误的。math/rand 适用于模拟和非确定性算法,几乎从不需要字节序列。Read 是 math/rand 和 crypto/rand 之间唯一共享的 API 部分,代码应该基本上总是使用 crypto/rand.Read。(math/rand.Read 和 crypto/rand.Read 存在问题,因为它们具有相同的签名; math/rand.Int 和 crypto/rand.Int 也都存在,但具有不同的签名,这意味着代码永远不会意外地将一个错认为是另一个。)
2、 移除 Source.Seed、Rand.Seed 和顶层的 Seed。顶层的 Seed 已在 Go 1.20 中废弃。Source.Seed 和 Rand.Seed 假定底层源可以由单个 int64 作为种子,这只对有限数量的源是真实的。具体的源实现可以提供具有适当签名的 Seed 方法,或者对于不能重新设置种子的生成器根本不提供;简单来说使用一个int64 作为种子没有普适性,不适合定义一个通用的接口。
注意,移除顶层 Seed 意味着顶层函数如 Int 将始终以随机方式而不是确定性方式生成。math/rand/v2 将不关注 math/rand 所关注的 [randautoseed](https://tip.golang.org/doc/go1.20#mathrandpkgmathrand "randautoseed") GODEBUG 设置;顶层函数的自动设置哦随机种子是唯一的模式。这反过来意味着顶层函数使用的具体 PRNG 算法是未指定的,可以在发布之间更改而不破坏任何现有代码。
3、 将 Source 接口更改为具有单个 Uint64() uint64 方法,取代 Int63() int64。后者过于拟合原始的 Mitchell & Reeds LFSR 生成器。现代生成器可以提供 uint64。
4、 移除 Source64,现在不再需要,因为 Source 提供了 Uint64 方法。
5、 在 Float32 和 Float64 中使用更直观的实现。以 Float64 为例,它最初使用 float64(r.Int63()) / (1