你在使用哪种编程语言?快来投票,亲手选出你心目中的编程语言之王 了解详情
写点什么

为什么 Go 语言没有泛型

2020 年 2 月 28 日

为什么 Go 语言没有泛型

Go 是一门语法元素少、设计简单的编程语言,简单的设计往往意味着较弱的表达能力,工程师也需要使用更多时间编写重复的逻辑。Go 语言从发布到今天已经过去了 10 多年,向 Go 语言添加泛型的讨论也从 2010 年一直持续到今天。社区对泛型的讨论非常多,呼声也非常高,下这里列举一些泛型相关的讨论和反馈:



很多人都认为 Go 语言永远不会加入泛型,然而这不是正确的结论,Go 语言很可能会在第二个主要版本中加入泛型4。所以本文要分析的问题是 —— 为什么 Go 语言到目前为止都没有泛型,以及这些原因是否已经被解决,又是如何被解决的。


如果你对 Go 语言的标准库稍微有一些了解,你能找到一些如下所示的函数签名:


package sort
func Float64s(a []float64)func Strings(a []string)func Ints(a []int)...
复制代码


上述函数都是 sort 包提供的,它们的功能非常相似,底层的实现也使用了近乎相同的逻辑,但是由于传入类型的不同却需要对外提供多个函数。Java 的泛型就解决了这个问题:


public class ArraySortViaComparable {    public <E extends Comparable> void insertionSort(E[] a) {        for (int i = 1; i < a.length; i = i + 1) {            Comparable itemToInsert = a[i];            int j = i;            while (j != 0 && greaterThan(a[j-1], itemToInsert)) {                a[j] = a[j-1]                j = j - 1            };            a[j] = itemToInsert;        }    }
private static boolean greaterThan(E left, Object right) { return left.compareTo(right) == 1; }}
复制代码


这段 Java 代码使用泛型数组作为参数实现了通用的数组排序逻辑,任意类型只要实现了 Comparable 接口,insertionSort 函数就能排序由该对象组成的数组。使用泛型能够减少重复的代码和逻辑,为工程师提供更强的表达能力从而提升效率。


既然泛型能够增强语言的表达能力,提升工程师的效率,那么为什么 Go 语言到目前为止也不支持泛型呢?本文总结了两个原因:


  • 泛型困境使我们必须在开发效率、编译速度和运行速度三者中选择两个;

  • 目前社区中的 Go 语言方案都是有缺陷的,而 Go 团队认为泛型的支持不够紧急;


上述两个原因导致 Go 语言没有在 1.x 版本中加入泛型。


泛型困境

泛型和其他特性一样不是只有好处,为编程语言加入泛型会遇到需要权衡的两难问题。语言的设计者需要在编程效率、编译速度和运行速度三者进行权衡和选择5,编程语言要选择牺牲一个而保留另外两个。



图 1 - 泛型困境


我们以 C、C++ 和 Java 为例,介绍它们在设计上的不同考量:


  • C 语言是系统级的编程语言,它没有支持泛型,本身提供的抽象能力非常有限。这样做的结果是牺牲了程序员的开发效率,与 Go 语言目前的做法一样,它们都需要手动实现不同类型的相同逻辑。但是不引入泛型的好处也显而易见 —— 降低了编译器实现的复杂度,也能保证源代码的编译速度;

  • C++ 与 C 语言的选择完全不同,它使用编译期间类型特化实现泛型,提供了非常强大的抽象能力。虽然提高了程序员的开发效率,不再需要手写同一逻辑的相似实现,但是编译器的实现变得非常复杂,泛型展开会生成的大量重复代码也会导致最终的二进制文件膨胀和编译缓慢,我们往往需要链接器来解决代码重复的问题;

  • Java 在 1.5 版本引入了泛型,它的泛型是用类型擦除实现的。Java 的泛型只是在编译期间用于检查类型的正确,为了保证与旧版本 JVM 的兼容,类型擦除会删除泛型的相关信息,导致其在运行时不可用。编译器会插入额外的类型转换指令,与 C 语言和 C++ 在运行前就已经实现或者生成代码相比,Java 类型的装箱和拆箱会降低程序的执行效率6



图 2 - 不同语言的决策


当我们面对是否应该支持泛型时,实际上需要考虑的问题是:我们应该牺牲工程师的开发效率、牺牲编译速度和更大的编译产物还是牺牲运行速度。


泛型的引入一定会影响编译速度和运行速度,同时也会增加编译器的复杂度,所以社区在考虑泛型时也非常谨慎。Go 2 的泛型提案在面对这个问题时没有进行选择,让具体实现决定是应该影响编译速度(单独编译不同的类型参数)还是运行时间(使用方法调用在运行时决定具体执行的函数)。


不紧急不完善

Go 语言团队认为加入泛型并不紧急7,更重要的是完善运行时机制,包括 调度器8、垃圾收集器等功能。作者在使用 Go 语言时,对泛型没有特别多的需求,只是在提供一些通用的抽象逻辑时不得不使用 interface{} 作为方法的参数,这不是一种很好的做法,但也是在当前语言限制下为数不多的方法。


社区中的大部分泛型提案都有各自的缺陷,所以不会被 Go 团队采纳,在这里我们为大家列出一部分提案,感兴趣的读者可以访问下面的链接了解更多的内容:



正是因为向 Go 语言中加入泛型并不是团队的首要工作,而过去的提案都有明显的缺陷,所以从 Go 语言发布 10 多年以来一直都没有支持泛型。


2019 年 7 月底,Go 团队发布了 Go 2 泛型设计的草稿 Contracts - Draft Design9,这个设计草稿建议增加参数多态来扩展 Go 语言,有了参数多态,函数能够接收的参数不再仅限于子类型关系(Subtyping),还可以有显式的结构约束(Structural constraint),下面的代码就约束了切片中的类型 T 需要满足 stringer 合约:


func Stringify(type T stringer)(s []T) (ret []string) {  for _, v := range s {    ret = append(ret, v.String()) // now valid  }  return ret}
复制代码


该提案从语法(Syntax)、类型约束(Type constraint)、类型推导(Type inference)和实现(Implementation)四个方面提出 Go 语言应该如何支持泛型:


  • 语法 —— 泛型、函数和方法是如何声明和使用的?

  • 类型约束 —— 如何定义类型约束?

  • 类型推导 —— 什么时候函数调用可以忽略类型参数?

  • 实现 —— 使用编译期替换还是运行时替换?


与之前的提案相比,这是 Go 团队目前能给出的最好方案,cmd/compile/internal/syntax: parse/print support for type parameters and contracts10 展示了如何通过修改编译器来支持提案中的语法,然而这也只是一个简单的原型,最终的实现和草案本身都需要经过社区的讨论。


总结

Go 语言从来没有旗帜鲜明地反对向语言中加入泛型这一特性,很多人对于 Go 的这一决策都有误解。到目前为止,Go 语言没有泛型的原因也可以简单总结成两点:


  • 泛型困境是所有编程语言都需要面对的,也是加入泛型之前不得不深思熟虑的;

  • 目前的多数泛型提案都有明显的缺陷,而且在 1.x 版本中,提升语言其他方面性能带来的收益比泛型带来的更多;


Go 2 的泛型草案暂时也没有解决这两个问题。它只是决定了引入泛型来增强语言的表达能力,提高程序员的生产力,但是却绕过了编译速度和运行速度的抉择问题,我们还不清楚最终到底会如何决策;最新的草案与之前的版本相比已经相对完善,但是还有很多的问题需要解决,例如:隐式约束(Implied constraints)、双重实现(Dual implementation)等。


作者相信 Go 社区能够做出相对合理的决策,并解决引入泛型带来的问题。到最后,我们还是来看一些比较开放的相关问题,有兴趣的读者可以仔细思考一下下面的问题:


  • Go 草案中的泛型设计与 Java 或者其他语言有哪些不同?

  • Go 语言中的哪些标准库可以被泛型重写?


本文转载自 Draveness 技术网站。


原文链接:https://draveness.me/whys-the-design-go-generics


2020 年 2 月 28 日 17:20440

评论

发布
暂无评论
发现更多内容

微服务架构下你的数据一致了吗?

码猿外

架构 微服务 数据一致性

ChaosBlade:从零开始的混沌工程(五)

郭旭东

Kubernetes 云原生 混沌工程

JVM系列之:再谈java中的safepoint

程序那些事

Java JVM JIT safepoint

《SSM深入解析与项目实战》目录与说明

谙忆

2.1 类加载器、 双亲委派模型 -《SSM深入解析与项目实战》

谙忆

SQL的三十而已—SQL30问

大唐小生

sql 技术人生

信创舆情一线--工信部开展网络安全技术应用试点示范工作

统小信uos

七的婚姻生活

徐说科技

设计模式之假如需要一百万个对象

架构师修行之路

Google Protocol Buffer 学习笔记

半亩房顶

protobuf

英特尔十代酷睿携手机械革命X3-S 纵享顺畅游戏之巅

最新动态

.net core快速开发平台,learun自主工作流引擎设计规范

力软.net/java开发平台

如何将FastDFS存储数据平滑迁移至XSKY对象存储?

XSKY融合存储

LeetCode题解:24. 两两交换链表中的节点,迭代,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

有它的加持,单机玩转百亿大数据不是梦!

易观大数据

1.2 了解MyBatis -《SSM深入解析与项目实战》

谙忆

操作系统和并发的爱恨纠葛

cxuan

Java 并发

C/C++陷阱与套路,当年就是折在这些地儿…

华为云开发者社区

c++ 设计 编辑 程序 陷阱

直播平台在贝壳找房中的实践与运用

陈威威

架构 分层架构 直播 分层思维 多元场景应用

如何设计一个优秀的组件

Lee Chen

前端进阶训练营

数据处理能力相差 2.4 倍?Flink 使用 RocksDB 和 Gemini 的性能对比实验

Apache Flink

flink

CDN百科10:快速上手阿里云DCDN全站加速,最新配置与购买优惠教程

阿里云Edge Plus

CDN 直播 网页加速

Gitlab 部署配置

wong

gitlab

设计模式之——单例模式你真的会吗?

诸葛小猿

设计模式 单例模式 Singleton 饿汉式 懒汉式

1.1 了解Spring框架 -《SSM深入解析与项目实战》

谙忆

Jessie’s产品经理系列1-基础能力篇

架构5班杨娟Jessie

产品经理 能力模型

【译】代码中如何写出更有意义的命名

Jackey

代码质量

设计模式中的单例模式并不完美

架构师修行之路

设计模式 单例模式 23种设计模式 高并发系统设计

联盟链有自己的路要走

Leonbond

区块链 联盟链 公有链

什么是深度强化学习?

华章IT

学习 智能体

零基础建网站必备技能,看这一篇就够了

北柯

程序语言 网站搭建 编程网站

围绕“三个问题”开展的网易云音乐数据基础建设

围绕“三个问题”开展的网易云音乐数据基础建设

为什么 Go 语言没有泛型-InfoQ