编程 Go 1.28 把 string(整数) 改成编译错误:string(65) 不再等于 "A"

2026-11-07 21:03:52

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 那样把「这里可能是想转数字文本」讲清楚。所以更省事的顺序是:

  1. 先在当前稳定版(go1.27.x)的代码上跑 go vet ./...;
  2. 把 stringintconv 报出的每一处收集起来,确认是「本想要数字文本」还是「确实要码点」;
  3. 数字文本改成 strconv.Itoa / strconv.FormatInt / fmt.Sprintf("%d", …),码点改成 string(rune(x)) 或 string('x');
  4. 再切到 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 为准。

复制全文 生成海报 Go 编译 类型转换

推荐文章

程序员茄子在线接单