IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    煎鱼发表于 2023-09-13 12:43:47
    love 0

    大家好,我是煎鱼。

    相信大家在接触 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))
    }

    输出结果:

    脑子进煎鱼了
    6e+11
    600000000000
    -0.28470407323754404
    

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

    如果是用 var 声明:

    var s = []string{"摸", "煎", "鱼"}
    
    func main() {
        fmt.Println(s)
    }

    正常输出:[摸 煎 鱼]

    如果是用 const 声明:

    const s = []string{"摸", "煎", "鱼"}

    将会报错:

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

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

    为什么不支持更多类型

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

    这是非常奇怪的。毕竟 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,认为:

    var each1 = []byte{'e', 'a', 'c', 'h'}

    可以正常运行。

    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)也参与了讨论,他例举了一个常量的经典骚操作。

    代码如下:

    - const ErrVerification = errors.New("crypto/rsa: verification error")
    + var ErrVerification = errors.New("crypto/rsa: verification error")

    在对应的 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/blog 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

    Go 图书系列

    • Go 语言入门系列:初探 Go 项目实战
    • Go 语言编程之旅:深入用 Go 做项目
    • Go 语言设计哲学:了解 Go 的为什么和设计思考
    • Go 语言进阶之旅:进一步深入 Go 源码

    推荐阅读

    • 互联网公司裁员的预兆和手段
    • 又有新功能!Go 将有生成新模板的 gonew 工具链
    • Go1.21 那些事:泛型库、for 语义变更、统一 log/slog、WASI 等新特性,你知道多少?


沪ICP备19023445号-2号
友情链接