被折磨了 13 年,Go 怎么解决再赋值的坑?

大家好,我是煎鱼。

最近在看 Go 的一些历史提案时,发现有个别很神奇的提案,已经提出来了许多年,但在如今依然没有关闭,并且不断地有人在讨论,但又解决不了。

有种 “很气又干不掉我的样子”,今天就由煎鱼带大家一起来看看是什么。

背景

今天本文介绍的 Go 提案《proposal: spec: various changes to :=》是经典中的经典,初学者学习时常犯的问题。

该提案自 2009 年提出,最近一次的激烈讨论是 2021 年:

代码原型如下:

func f() (err os.Error) {
    v, err := g()
    if err != nil {
        return
    }
    if v {
        v, err := h()
        if err != nil {
            return
        }
    }
}

这段代码的问题在于 if 语句中的 := 会导致产生一个新的 err 变量,该变量会使得返回参数被覆盖。

也就是 Go 里的 := 重新赋值的逻辑,会导致参数被覆盖,从而引起隐藏问题。

新提案

如开头所说,这是一个经过了 13 年,在 2022 年依然没有结局的提案。

总结整个提案和其他内的思路,大家一共提出了如下几种解决方案或思路:

  • 加语法糖。
  • 干掉语法。
  • 定规范。

加语法糖

这个想法删除了重新声明 := 语法,并添加了新的 : 和 :: 语法,用于新变量声明。

如下代码:

package bar

func foo() {
   var x, err = f()
   ...
   // 这里 “:err” 表示上面声明的 err。 
   var y, z, :err = g()
   ...
   {
    // 实际上,:err 表示代码区块里的已经声明的 err。
       var w, :err = h()
       ...
       // ::err 表示包级别声明的 err。
       var u, v, ::err = j()
       ...
       // 这个“err”是一个新的声明。
       var m, n, err = k()
       ...
   }
}

上述代码中给出了三种案例,分别是:

  • var :err = x:表示最近一个作用域声明的 err,原意是指上面一个声明的 err,因此你会发现在代码区块和外,代表着不同的结果。
  • var ::err = x:表示包级别声明的 err。
  • var err = x:表示一个新的声明。

干掉语法

在另外一个提案《proposal: Go 2: let := support any l-value that = supports》中 Go 语言之父 @
Rob Pike 直接表示想干掉 := 这个重新赋值的方式,而不是再修修补补,加一堆会更复杂。

如下图:

我认为我们应该以消除重新声明为目标,如果我们能够建立一个更平稳的错误处理模型,那么重新声明就变得不那么引人注目了。不过这不会很快发生。

删除功能而不是增加功能。

(大呼:less is more)

单行多次声明

首先修改重新赋值的语义,:= 左边的所有标识符总是被声明为新的变量,在同一个块内重新声明是不允许的。

如下代码:

a, err := foo()
b, err := foo() // 编译错误,因为 var err 已在此块中声明

第一行声明正常,第二行由于在同一个代码区块重新声明了,因此会出现编译错误,因为已经声明过了。

接着增加语法特性,允许在一行中混合使用 = 和 :=。如下代码:

// a 和 err 被声明和初始化(相当于:a, err := foo()
a:=, err:= foo()

// b 被声明和初始化,而 err 只被赋予了一个新值
b:=, err= foo()
if true {
    // c 在 if 块中声明并初始化,并为 err 分配一个新值
    c:=, err= foo()
}
if true {
    // d 和 err 在 if 块中声明,err 被隐藏
    d:=, err:= foo()
}

允许单行进行多次声明,本质上是明确了声明的范围,会提高代码可读性的复杂度。

总结

今天这篇文章给大家介绍了一个 13 年前(2009 年)就被发现的神坑。当初最早学习 Go 时,也碰到很多教程、文档,同学会遇到这个重新赋值声明的神坑。

实际上上述的 3 个方案,看起来是从不同的角度补全了这个重新声明的语法糖,但也加大了复杂度。

也许直接干掉,也可能是个不错的选择?

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

推荐阅读


煎鱼的清汤锅
今天写代码了吗 :-) 博客地址:[链接]
8.2k 声望
12.7k 粉丝
0 条评论
推荐阅读
一分钟搞明白!快速掌握 Go WebAssembly
最近因为各种奇怪的原因,更多的接触到了 WebAssembly。虽然之前很多博客也翻过写过各种文章,但总感觉欠些味道。于是今天梳理了一版,和大家一起展开学习。

煎鱼1阅读 605

一文搞懂秒杀系统,欢迎参与开源,提交PR,提高竞争力。早日上岸,升职加薪。
前言秒杀和高并发是面试的高频考点,也是我们做电商项目必知必会的场景。欢迎大家参与我们的开源项目,提交PR,提高竞争力。早日上岸,升职加薪。知识点详解秒杀系统架构图秒杀流程图秒杀系统设计这篇文章一万多...

王中阳Go34阅读 2.6k评论 1

封面图
一个HTTP请求的曲折经历
作为程序员的我们每天都在和网络请求打交道,而前端程序员接触的最多的就是HTTP请求。平时工作中,处理网络请求之类的操作是最多的了。但是一个请求从客户端发出到被服务端处理、再回送响应,再被客户端接收这一...

nero24阅读 5.1k评论 1

Nginx 一网打尽:动静分离、压缩、缓存、黑白名单、跨域、高可用、性能优化...
早期的业务都是基于单体节点部署,由于前期访问流量不大,因此单体结构也可满足需求,但随着业务增长,流量也越来越大,那么最终单台服务器受到的访问压力也会逐步增高。时间一长,单台服务器性能无法跟上业务增...

民工哥23阅读 1.1k

封面图
Golang 中 []byte 与 string 转换
string 类型和 []byte 类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间的内在联系来拨开迷雾。

机器铃砍菜刀24阅读 58.4k评论 2

最好用的 python 库合集
🎈 分词 - jieba优秀的中文分词库,依靠中文词库,利用词库确定汉子之间关联的概率,形成分词结果 {代码...} 🎈 词云库 - wordcloud对数据中出现频率较高的 关键词 生成的一幅图像,予以视觉上的突出 {代码...} 🎈 ...

tiny极客11阅读 2.9k评论 2

封面图
计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

程序员大彬14阅读 1.8k

8.2k 声望
12.7k 粉丝
宣传栏