10 年了!Go 常量为什么只支持基本数据类型?

大家好,我是煎鱼。

相信大家在接触 Go 这门编程语言时,就会学到常量这个知识点。

各大编程语言会教你,常量是不可变变量的一种类型。只要定义了常量,你就可以安心的用他。不用担心值在哪里就被程序莫名奇妙的给改了。

常量的使用例子

如下例子:

go 复制代码
const s string = "脑子进煎鱼了"

func main() {
	fmt.Println(s)

	const n = 500000000

	const d = 3e20 / n
	fmt.Println(d)

	fmt.Println(int64(d))

	fmt.Println(math.Sin(n))
}

输出结果:

diff 复制代码
脑子进煎鱼了
6e+11
600000000000
-0.28470407323754404

你可能会发现一个奇怪的点。那就是例子里都是基本的数据类型。那能不能用复杂点的数据类型呢,例如 Go 里比较经典的切片。

如果是用 var 声明:

go 复制代码
var s = []string{"摸", "煎", "鱼"}

func main() {
	fmt.Println(s)
}

正常输出:[摸 煎 鱼]

如果是用 const 声明:

go 复制代码
const s = []string{"摸", "煎", "鱼"}

将会报错:

go 复制代码
./prog.go:7:11: []string{...} (value of type []string) is not constant

因为在 Go 中,仅支持字符、字符串、布尔和数值类型的常量。这和其他编程语言间多多少少还是有点不一样的。

为什么不支持更多类型

为什么在 Go 中常量只支持这几种基础类型,为什么没法支持复杂类型,甚至是所有类型?

这是非常奇怪的。毕竟 PHP 都能支持:

php 复制代码
class MyClass
{

    const ABC = array('A', 'B', 'C');

    const A = '1';

    const B = '2';

    const C = '3';

    const NUMBERS = array(
        self::A,
        self::B,
        self::C,
    );

}

这又是出于什么缘由?

已有 10 年老提案

我认真翻阅了 Go issues 等相关资料,终于发现竟然在 2013 年(10 年前),就已经有人提出过这个提案:

提出者 @RickySeltzer,认为:

go 复制代码
var each1 = []byte{'e', 'a', 'c', 'h'}

可以正常运行。

go 复制代码
const each2 = []byte{'e', 'a', 'c', 'h'}

不行,直接报错:prog.go:7: const initializer []byte literal is not a constant

他认为这就是 Go 语言规范和设计上的缺陷,得改,得支持!

拒绝的论据是什么

对此也有各种争论,老大哥 @Robert Griesemer,10 年前已 在为 Go 贡献,现在依然还在...他对 Go 常量只支持基本类型这个设计给出了定论。

基于如下原因:

  • 首先这不是语言的缺陷,也不是设计的缺陷。Go 语言的常量是故意设计成只支持基本类型。
  • 基本类型到复杂类型的改变,这种变化的影响比我们看到的要深远得多。需要考虑很多问题。
    • 常量 channel、常量指针、常量 maps、常量 slice 都要支持吗?
    • 先支持常量 array 和 常量 struct?
    • 要支持到什么程度,以什么来作为标准决定?
  • 设计上,Go 团队期望类型系统(包括常量的含义)相对简单,以免在编译时出现问题。
    • 常量更多类型的支持,会增加复杂性。不清楚这样做的好处是否值得(ROI)。
    • 常量是否可以寻址?const 数组的元素本身必须是 const 吗?

汇总一下,潜台词就是:

  1. 设计如下:常量只支持基本类型,是 Go 设计上就是这么决定的。
  2. ROI 要衡量一下:没有支持更多的复杂类型,是认为好处不多。也没有想清楚这块的缘由。
  3. 少即是多:类型系统要保持少即是多(less is more)。

实战中的骚操作

在相关提案的 issues 中看到欧神(@Changkun Ou)也参与了讨论,他例举了一个常量的经典骚操作。

代码如下:

go 复制代码
- const ErrVerification = errors.New("crypto/rsa: verification error")
+ var ErrVerification = errors.New("crypto/rsa: verification error")

在对应的 Go 工程中可能是这么使用:

go 复制代码
import "cropto/rsa"
func init() {
 rsa.ErrVerification = nil
}

这个场景我还真见过,一开始想定义 const,结果不支持。只能被迫 var。这不,就开天窗了。

总结

Go 核心团队在综合衡量 ROI 后,认为常量支持更多的类型在可预见的未来不会改变。因为将常量的概念扩展到基本类型之外将是一个重大变化,会产生各种影响。

在此刻(Go1)改变语言的门槛非常高,需要有明确的定义,要有完整的提案以及对成本和效益等的详细分析。

如果出现,只可能在未来的 Go2。但结合 rsc 最新的结论,没有 Go2 的话,那如果未来真的做,那就是通过 GODEBUG 和 go.mod 的 go 版本号来实现了。

常量更多类型的支持,虽然已经提出了 10 年。但目前 Go 核心团队推进乏力,想必,可能还要个 5 年吧。

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blo... 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

Go 图书系列

推荐阅读

相关推荐
研究司马懿9 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大1 天前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰1 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘1 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤1 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo