当前位置: 首页 > 后端技术 > PHP

Go1.20将禁止循环导入匿名接口!这是违反Go1兼容性承诺的真实案例,..

时间:2023-03-30 05:14:56 PHP

大家好,我是炸鱼。最近临近新版本发布节点,在看Go1.20的新特性《spec: disallow anonymous interface cycles》,发现一个比较坑爹的操作...没想到还能这么用之前,很有趣,所以我想和你分享。在Go规范中,允许将接口类型(interface{})嵌入到其他声明的接口中,这就是大名鼎鼎的套娃神器:combination。Go标准库中Matryoshka接口类型的一个经典例子如下:error}actual上面的扩展是:typeReadCloserinterface{interface{Read(p[]byte)(nint,errerror)}interface{Close()error}}一切看起来都那么美好,似乎体现了优秀的本质去的地方。计划赶不上变化。匿名接口循环导入在实际代码中,这种支持有循环引用的用法。下面是一个简单的例子:typeIinterface{m()interface{I}}这段代码声明接口类型I,然后包含m(),然后包含接口I。这将是一个“永动机”,永远不会停止。在开源的GitHub中,它是真实存在的。如项目gozelus/zelus_rest的代码:typeMySQLDbinterface{execSQLTable(ctxcontext.Context,namestring)interface{whereSQLinsertSQLselectSQLfindSQLorderSQLclausesSQL}Begin()interface{MySQLDbRollback()Commit()}}如projectvetcher/go-astra的代码:typeComplexInterfaceinterface{A(ainterface{B()ComplexInterface})interface{C()D()}}其实很迷惑,意思是接口可以无限嵌套,并且内部方法。但是作者写这段代码的时候,目的可能不是这样,导致用户误用。推广简单易用的瀑布式编程的Go有没有问题,支持循环导入匿名接口是否合规?其实并不是。早在2016年,Proposal:TypeAliases中的Typecycles部分是这样定义的:它明确指出类型别名必须能够“扩展”,但没有办法像T=*T别名那样“扩展”。应用到当前问题,如果上面的T是I(接口类型),则同理I=*I,这个过程永远无法终止。社区讨论经过热烈讨论,基于以下几点决定采纳该提议,即在新版本中禁用Go匿名接口的循环导入,改为对所有嵌入式接口的有限扩展。禁用后,将拒绝以下三种类似的写法。第一种:typeBinterface{I}typeIinterface{m()interface{B}}第二种:typeB=interface{I}typeIinterface{m()interface{B}}第三种:typeB=interface{I}typeIinterface{m()B}Go1CompatibilityPromiseGo1CompatibilityPromise的核心是Go1CompatibilityPromise。从任何角度来看,禁用此功能都是一项重大更改(不向后兼容),并且绝对违反了兼容性承诺。相信在公共项目库中,几乎没有人使用过这种匿名接口循环导入的方式,其使用也很少(几乎为0),除了上面提到的gozelus/zelus_rest项目外,这个模块好像被引用了没有人。在权衡利弊后,rsc认为取消该功能可以更好地提高代码的简洁性,并确定该功能的禁用将与之前同步推进。如下:Go1.20:Go编译器默认会拒绝这些interfacecycles,但是可以使用gobuild-gcflags=all=-d=interfacecycles来build,保证旧代码的正常编译。如果有人在发布候选期间向Go团队报告大量损坏,则此更改将被撤消。Go1.22:在1.22版本之后,-d=interfacecycles标志将被删除,旧代码将不再构建此功能。如果有人报告问题,可以讨论或推迟删除,以便有更多时间进行改造。链式调用模式有一个经典的设计模式叫做:链式调用,也叫方法链。比如在etcdsdk中,经常会在Watch、Next等相关接口中看到。在Go中,可以这样写:typeNexterinterface{Next(Input)(interface{Nexter},error)Done()Output}一旦禁用,就不能这样进行匿名嵌套了。强烈推荐使用如下方法:typeNexterinterface{Next(Input)(Nexter,error)Done()Output}也推荐将此类节点声明为Node:typeNodeinterface{Parent()NodeFirstChild()NodeChildren()[]Node}套娃也必须要起名字,不能变成“无名”。综上所述,原来对匿名接口循环导入的支持,本质上违背了Go一贯的简洁明了的设计理念。如果在Go项目中大量使用,稍不注意就会有副作用,禁用就好了。此功能更改的代码已提交。如果按照rsc的计划我们将在Go1.20或Go1.21中看到这个新功能,Go1.22或Go1.24将被正式删除。值得注意的是,正因如此,Go团队应该是在第一时间打破了对Go1兼容性的承诺,进行了破坏性的改动,在推进方式上采取了渐进的模式。这还是值得我们关注的,毕竟……破窗效应?文章持续更新中。可以微信搜索【脑补炸鱼】阅读。本文已收录在GitHubgithub.com/eddycjy/blog中。学习Go语言可以看Go学习地图和路线。欢迎星星提醒。Go书系列Go语言入门系列:初探Go项目实战Go语言编程之旅:深入使用Go做项目Go语言设计哲学:理解Go的Why与设计思维Go语言进阶之旅:进一步深入-深度Go源码推荐阅读Go1。20号更新了两次时间,终于不用背2006-01-0215:04:05了!打脸兄弟们,Go1.20竞技场来了!Go十年,终于想起统一日志库!