编程 Go 1.28 将拒绝 string(int):规则边界、go.mod 生效点与 go fix 顺序

2026-11-01 21:05:43

Go 1.28 将拒绝 string(int):规则边界、go.mod 生效点与 go fix 顺序

2026-09-23 的 Proposal Review Meeting 上,spec: remove string(int) #3939 被接受(Proposal-Accepted)。提案人是 robpike,这个提案最早在 2012 年就提过。

string(65) 为什么反直觉

整数转字符串最初是为格式化打印加进语言的。它的行为是:string(65) 得到 "A",按 Unicode 码点 65 解释,而不是 "65"。不熟悉的人第一次写出来,通常以为会拿到十进制数字。

更麻烦的是不一致。string(0xD800) 能编译通过,结果是 "\uFFFD",因为代理区不是合法的 UTF-8;但直接写字面量 "\uD800",编译器会拒绝。同一个值,走转换是静默替换成替换符,走字面量是编译错误。

1.28 起的规则

从语言版本 1.28 开始,整数转字符串只在以下情况允许:值的基底类型是 rune 或 byte,或者值本身是未类型化的 rune 常量。

string(rune(65))          // OK
string([]byte{'a'})       // OK
string(int32(65))         // OK,rune 的基底类型是 int32
string(uint8(65))         // OK,byte 的基底类型是 uint8
string('A')               // OK,untyped rune literal

type myRune rune
string(myRune(65))        // OK,基底类型是 rune

string(65)                // 被拒,untyped integer literal

边界落在「基底类型」和「是否类型化」上。命名类型 myRune 可以通过,因为它的基底类型是 rune;int32 也能通过,因为 rune 的基底类型就是 int32;uint8 同理对应 byte。'A' 是字符字面量,属于未类型化的 rune 常量,保留。而裸的 65 是未类型化整数字面量,不再接受。

兼容性由 go.mod 的 go 指令决定

工具链升到 1.28,不代表旧模块立刻编译失败。模块 go.mod 中的 go 指令小于等于 1.27 时,仍按旧规则编译;把 go 指令改成 1.28 及以后,这些转换才会被拒绝。也就是语言版本跟着模块声明走,而不是跟着本机工具链走。

不必等到 1.28 才发现问题。go vet 的 stringintconv 检查从 Go 1.15 起就会报这类转换:

go vet ./...

现在跑一遍,就能列出将来会被拒绝的调用点。

两种 fix 的语义不同

x/tools 的 stringintconv analyzer 文档写明:As of Go 1.28, such conversions are rejected by the compiler in files whose Go version is go1.28 or later. 它提供两个 fix。

  1. 保留原有行为:先转 rune,写成 string(rune(x)),由 go fix 应用。这里有一个边界:如果 x 可能宽于 32 位,截断成 rune 可能把原本无效的码点变成有效码点。这种情况下 fix 会改用 fmt.Sprintf("%c", x),对所有超出有效码点范围的值都产出 "\uFFFD",与原来的 string(x) 一致。

  2. 按十进制格式化:fmt.Sprintf("%d", x)。这通常是写代码的人本意,但它会改变行为——string(65) 原本是 "A",改完是 "65"。

选哪个是语义选择:保留旧行为,还是修正成本意。

先 go fix,再改 go 指令

迁移一个模块到 Go 1.28,顺序不能反:必须在把 go.mod 里的 go 指令改上去之前先跑 go fix。

原因很直接:go 指令一旦改成 1.28,包含这些转换的包就不再编译,go fix 也就无法加载并分析它们。顺序反了,只能先把指令退回去。

常见替换

var n1 int
str1 := string(rune(n1)) // 想按码点求值

var n2 int
str2 := strconv.Itoa(n2) // 想要数字的十进制字符串

var n3 int64
str3 := strconv.FormatInt(n3, 10)

1.27 上的实测行为可以对照:

string(65)       // "A"
string(rune(65)) // "A"
strconv.Itoa(65) // "65"
string(128512)   // "😀"
string(-1)       // ""(无效码点)

提案目标是在 Go 1.28 引入,若不延误大约在明年 2 月。

相关链接:

  • 提案:https://github.com/golang/go/issues/3939
  • stringintconv vet 检查:https://github.com/golang/go/issues/32479
复制全文 生成海报 Go 语言特性 迁移

推荐文章

程序员茄子在线接单