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。
保留原有行为:先转
rune,写成string(rune(x)),由go fix应用。这里有一个边界:如果x可能宽于 32 位,截断成rune可能把原本无效的码点变成有效码点。这种情况下 fix 会改用fmt.Sprintf("%c", x),对所有超出有效码点范围的值都产出"\uFFFD",与原来的string(x)一致。按十进制格式化:
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
stringintconvvet 检查:https://github.com/golang/go/issues/32479