Go 1.28 把 string(整数) 改成编译错误:string(65) 不再等于 "A"
Go 1.28 的开发分支里,类型检查器收紧了 string(x) 的规则:当 x 是整数类型时,只有当 x 明确是 byte / rune 类型,或者是一个无类型 rune 常量,才允许转换,其余整数类型直接编译不过。对应提案是 golang/go#3939,编译器前端提交为 f095da59e4,Release 概览里写的是 string(int) restricted。
string(65) 得到的是 "A"
Go 规范里,string(x) 在 x 为整数类型时按 Unicode 码点转换,不是按十进制数字转字符串:
fmt.Println(string(65)) // A
fmt.Println(string(0x4E2D)) // 中
无效码点会被替换为 "\uFFFD":
fmt.Println(string(-1)) // �
fmt.Println(string(0xD800)) // �(代理区码点)
这是多年来最容易读错的一处语法。想拼出 "65" 必须走 strconv,但代码里写 string(n) 编译完全通过,只是运行时结果和作者预期不一致。
go vet 的 stringintconv 检查从 Go 1.15 起就在提示这类写法,但它是 vet 警告,不是编译错误,很多项目也没在 CI 里跑 vet,警告就一直存在。
Go 1.28 起:编译期限制
Go 1.28 把这条限制前移到了类型检查阶段——只有以下几种 x 能进 string(x):
x的静态类型是byte(即uint8)x的静态类型是rune(即int32)x是无类型 rune 常量
int、int64、uint、uint64 等一律拒绝。实现上把 constConvertibleTo 改成了操作数方法(operand method),转换可行性判断直接落在操作数上。
典型踩坑场景
这三类写法在旧代码里很常见,Go 1.28 下全部报错:
// 1. 拼用户 ID
uid := 10086
s := "user" + string(uid) // 编译失败
// 2. HTTP 状态码
resp, _ := http.Get(url)
code := string(resp.StatusCode) // 编译失败,StatusCode 是 int
// 3. 长度
buf := make([]byte, 16)
n, _ := f.Read(buf)
s := string(len(buf)) // 编译失败
注意第 1 种和第 3 种:作者本来想要的就是十进制文本,string() 从来给不出这个结果。第 2 种更隐蔽,string(resp.StatusCode) 在 200 时得到的是一个不可打印字符,不是 "200"。
正确替换
import "strconv"
uid := 10086
s := "user" + strconv.Itoa(uid) // "user10086"
code := strconv.Itoa(resp.StatusCode) // "200"
code64 := strconv.FormatInt(int64(v), 10) // int64 / 其他整型
s = fmt.Sprintf("%d", len(buf)) // 或 strconv.Itoa(len(buf))
fmt 的动词要分清:
fmt.Sprintf("%d", 65) // "65" —— 十进制文本
fmt.Sprintf("%c", 65) // "A" —— 按 Unicode 码点
要码点就用 %c(或显式 string(rune(65))),要数字文本就用 %d / strconv。两者不要再混用。
允许保留的写法
确实是按码点转换的代码,加上显式类型即可通过:
s1 := string(rune(65)) // "A"
s2 := string('A') // "A",无类型 rune 常量
var b byte = 65
s3 := string(b) // "A",byte 类型
var r rune = 0x4E2D
s4 := string(r) // "中"
string('A') 这类字面量写法不受影响,因为 'A' 是无类型 rune 常量。
迁移做法
直接上 Go 1.28 只会得到编译错误,错误信息不会像 vet 那样把「这里可能是想转数字文本」讲清楚。所以更省事的顺序是:
- 先在当前稳定版(go1.27.x)的代码上跑
go vet ./...; - 把
stringintconv报出的每一处收集起来,确认是「本想要数字文本」还是「确实要码点」; - 数字文本改成
strconv.Itoa/strconv.FormatInt/fmt.Sprintf("%d", …),码点改成string(rune(x))或string('x'); - 再切到 Go 1.28 编译验证。
一步到位的批量替换不要直接做,string(uid) 和 string(r) 在文本上长得一样,语义完全不同,得逐处判断。
版本现状
截至 2026-11:go1.27.0 于 2026-08-19 发布,最新补丁为 go1.27.3(2026-10-19)。Go 1.28 仍在开发分支,尚未发布,这条规则的具体行为和错误信息可能还会微调,以正式 release notes 为准。