Golang编译器DIY,手搓 if err != nil { return err } 语法糖
前序
在go的社区里,下面这三行代码是被吐槽的最多的
if err != nil {return err
}
从代码之整洁美观的角度看,这样的写法也是让人不舒服的。尤其是 当有很多错误需要处理的时候,就会发现通篇都是这三行。
所以想着看看修改一下编译器,优化这一写法。
预期达到的效果如下:
func Aaa(info string) (err error) {err = NewError(info)?//todo something
}
它等价于下面这段函数
func Aaa(info string) (err error) {err = NewError(info)if err != nil {return}//todo something
}
思路
go的编译过程大概分为这么几个阶段:
- 扫描解析源文件
- 类型检查和AST生成
- 生成SSA中间代码,并进行一定优化
- 生成机器码
基于这个过程,我们的"?"号语法糖,只需要在编译器解析源码的时候,将?号扩展为if err != nil { return } 即可
拉取go的源代码
在正式开始开工之前,需要在本地先安装一个go的执行环境,并且尽量用最新的版本。然后clone go的源码包。
下面我们所有的操作,都相对于这个目录来完成。
git clone https://github.com/golang/go.git
注意,你本地已经安装的go的版本,尽量只比你要编译的版本小一个版本号。
比如我这里用go version go1.21.11 darwin/amd64编译go version go1.22.0 darwin/amd64
代码拉下来后,可以先编译一下,确定默认编译不会出问题。
- all.bash 编译完成后,会自动进行测试,时间还是比较长的。并且给了多系统的命令文件 all.bat all.rc
- make.bash 仅编译,我的电脑大约20s就能编译完成。
cd go/src//编译并测试
./all.bash//仅编译
./make.bash
编译完成后,可以在bin目录下看到go文件,运行下面的命令,打印版本号,表示编译成功。
- 为了和本地环境区分开,需要指定GOROOT为我们下载的go源码的文件夹路径。
GOROOT=<go path> bin/go version//输出:go version go1.22.0 darwin/amd64
增加 ? 标识符 和 具体语法节点
首先增加 ?号标识符,在syntax目录下,这个目录的主要功能就是做scan和parser。
- 路径:
src/cmd/compile/internal/syntax/tokens.go - 在token里面增加一个_RetErr用来表示?号。注释必须按照下面的格式写,自动生成需要。
// go:generate stringer -type token -linecomment tokens.goconst (_ token = iota..._Semi // ;_RetErr // ?_Colon // :...
)
上边的注释 // go:generate stringer -type token -linecomment tokens.go表示我们需要go generate一下。
- stringer 较新的版本中这个包是内置的,通常不需要安装,如果提示不存在,则手动安装一下。
//syntax目录下执行go generate tokens.go
查看token_string.go文件,如下图,将我们新加的token也生成上去了。

我们还需要创建一个?号对应的具体语法树的节点,因为我们这里做的是一个语法糖,所以直接组合一下if对应的IfStmt结构就可以了,如下:
IfStmt struct {Init SimpleStmtCond ExprThen *BlockStmtElse Stmt // either nil, *IfStmt, or *BlockStmt
stmt
}RetErrStmt struct {IfStmt
}
简单介绍一下if结构的几个字段的作用:
- Init:在执行if条件判断前的代码块,如:
if _,ok:=get();ok{ todo }中,_,ok:=get()部分就是Init,它可以为空。 - Cond:判断条件,
- Then:条件为真时,执行这个代码。
- Else:条件为假时,执行的代码,可以为空。
语法解析
go的源码解析主要有两个结构体:
- scanner :负责按照token维度扫描源文件
- parser:将扫描的token组装成具体语法树
我们先增加对于?号标识符的扫描识别,src/cmd/compile/internal/syntax/scanner.go
func (s *scanner) next() {
...
case ';':s.nextch()s.lit = "semicolon"s.tok = _Semicase '?':s.nextch()s.tok = _RetErr
...
}
然后增加对?的语法解析,我们参考IfStmt的解析方法:
- 路径:
src/cmd/compile/internal/syntax/parser.go - stmtOrNil : 在这个函数中增加标识符的解析方法
- retErr :实际上我们增加了一个IfStmt的语法糖。
func (p *parser) stmtOrNil() Stmt {
...
case _RetErr:return p.retErr()case _If:return p.ifStmt()
...
}func (p *parser) retErr() *RetErrStmt {if trace {defer p.trace("ifStmt")()}//判断条件:err != nilcondExpr := &Operation{X: &Name{Value: "err"},Op: Neq,Y: &Name{Value: "nil"},}condExpr.pos = p.pos()// 表示 Then 块: returnthenBlock := &BlockStmt{List: []Stmt{&ReturnStmt{//Results: &Name{Value: "err"},},},Rbrace: p.pos(),}//组装RetErrStmts := new(RetErrStmt)s.pos = p.pos()s.Cond = condExprs.Then = thenBlock//继续向下扫描p.next()//如果存在else则继续解析if p.got(_Else) {switch p.tok {case _If:s.Else = p.ifStmt()case _Lbrace:s.Else = p.blockStmt("")default:p.syntaxError("else must be followed by if or statement block")p.advance(_Name, _Rbrace)}}return s
}
边界判断
go对写法有比较严格的要求,尤其是换行和边际的判断,所以我们需要告诉编译器,?号不需要处理边际,因为已经解析成了其他的结构。
同样在parser.go文件中的stmtList函数:
func (p *parser) stmtList() (l []Stmt) {if trace {defer p.trace("stmtList")()}for p.tok != _EOF && p.tok != _Rbrace && p.tok != _Case && p.tok != _Default {s := p.stmtOrNil()p.clearPragma()if s == nil {break}l = append(l, s)//跳过RetErrStmt的检查if _, ok := s.(*RetErrStmt); ok {continue}//?;} 都属于正常的边界。if !p.got(_Semi) && p.tok != _RetErr && p.tok != _Rbrace {p.syntaxError("at end of statement")p.advance(_Semi, _Rbrace, _Case, _Default)p.got(_Semi) // avoid spurious empty statement}}return
}
至此,一个初步的 ? 号语法糖已经制作完成,用make.base重新编译项目。
测试
我么在这个go目录下,新建一个测试文件reterr.go,内容如下:
- 因为源码中我们处理else的情况,所有理论上
?else{}和?else if xx {}也同样是支持的。 - EasyRetError:简单返回错误,可以看到代码量明显减少
- IfElseRetError和MulIfElseRetError:可以继续做else判断,并且不影响返回多个值
package mainimport ("errors""fmt"
)func NewError(text string) error {if text == "" {return nil} else {return errors.New(text)}
}
func EasyRetError(info string) (err error) {err = NewError(info)?return
}
func IfElseRetError(info string)(str string, err error){err = NewError(info)?else{return info,nil}
}
func MulIfElseRetError(info string,ok bool)(str string, err error){err = NewError(info)?else if ok{return "test",nil}else{return "success",nil}
}
在main函数中,我们写一下预期的结果,不符合预期则 panic。
func main() {var err errorvar info = ""//简单情况测试if err = EasyRetError("Err");err == nil {panic("EasyRetError.Err")}if err = EasyRetError("");err != nil {panic("EasyRetError.nil")}//if else 分支情况测试if info,err = IfElseRetError(info); err != nil || info != "" {panic("IfElseRetError.err not nil")}info = "test"if info,err = IfElseRetError(info); err == nil || info != "" {panic("IfElseRetError.info = test")}// 多分支测试info = "test"if _,err = MulIfElseRetError(info,true); err == nil {panic("MulIfElseRetError.err not nil")}if info,err = MulIfElseRetError("",true); err != nil || info != "test" {panic("MulIfElseRetError.test")}if info,err = MulIfElseRetError("",false); err != nil || info != "success" {panic("MulIfElseRetError.success")}fmt.Println("success")
}
用我们重新编译的go,运行上边的代码:
GOROOT=~/project/work/github/go bin/go run reterr.go
测试成功
相关文章:
Golang编译器DIY,手搓 if err != nil { return err } 语法糖
前序 在go的社区里,下面这三行代码是被吐槽的最多的 if err ! nil {return err }从代码之整洁美观的角度看,这样的写法也是让人不舒服的。尤其是 当有很多错误需要处理的时候,就会发现通篇都是这三行。 所以想着看看修改一下编译器…...
图解多头注意力机制:维度变化一镜到底
目录 一、多头注意力机制概述二、代码实现1. pyTorch 实现2. tensorFlow实现 三、维度变化全流程详解1. 参数设定2. 维度变化流程图3. 关键步骤维度变化 四、关键实现细节解析1. 多头拆分与合并2. 注意力分数计算3. 掩码处理技巧 五、完整运行示例六、总结与常见问题1. 核心优势…...
.NET_Prism基本项目创建
Prism简述 模块(Module):独立的功能单元,可动态加载。依赖注入(Dependency Injection,DI):通过 IoC 容器(如 Unity、Autofac)管理模块之间的依赖关系。&#…...
BigEvent项目后端学习笔记(一)用户管理模块 | 注册登录与用户信息全流程解析(含优化)
📖 模块概述 用户管理模块是系统的核心基础模块,包含 注册、登录、用户信息维护 等功能。本模块涉及 JWT Token认证、密码加密存储、文件上传 等关键技术点,是理解前后端分离架构中安全与数据交互的典型实践。本篇对于原项目进行了代码优化&…...
[ISP] 人眼中的颜色
相机是如何记录颜色的,又是如何被显示器还原的? 相机通过记录RGB数值然后显示器显示RGB数值来实现颜色的记录和呈现。道理是这么个道理,但实际上各厂家生产的相机对光的响应各不相同,并且不同厂家显示器对三原色的显示也天差地别&…...
解锁MySQL 8.0.41源码调试:Mac 11.6+CLion 2024.3.4实战指南
文章目录 解锁MySQL 8.0.41源码调试:Mac 11.6CLion 2024.3.4实战指南前期准备环境搭建详细步骤安装 CLion安装 CMake 3.30.5准备 MySQL 8.0.41 源码配置 CMake 选项构建 MySQL 项目 调试环境配置与验证配置 LLDB 调试器启动调试验证调试环境 总结与拓展 解锁MySQL 8…...
celery入门
按照Celery 官方文档,用 Django Celery Redis 写的一个简单项目 如需转载,标记出处 环境准备 1. 安装依赖 pip install django celery redis 创建 Django 项目 1. 创建 Django 项目和 APP django-admin startproject myproject cd myproject python …...
关于xcode Project navigator/项目导航栏的一些说明
本文基于 xcode12.4 版本做说明 首先要明确一点,导航栏这里展示的并不是当前工程在电脑硬盘中的文件结构,它展示的是xxxxxx.xcodeproj/project.pbxproj文件(后文简.pbxproj文件)中的内容。我们在导航栏中的操作就是修改该文件,有些操作会修…...
深度解析扣减系统设计:从架构到实践
背景 在当今数字化业务蓬勃发展的时代,扣减系统在众多业务场景中扮演着关键角色。无论是电商平台的库存扣减,还是金融领域的资金扣减、积分系统的积分扣减,一个高效、可靠且数据一致的扣减系统都是业务稳健运行的基石。本文将深入探讨扣减系…...
视觉定位项目中可以任意修改拍照点位吗?
修改拍照点位不是那么简单 1. 背景2. 修改拍照点位意味着什么?3. 如何解决这个问题? 1. 背景 在视觉定位的项目中,会遇到这么一种情况:完成三步(9点标定,旋转中心标定,示教基准)之…...
深度学习常用操作笔记
深度学习常用操作笔记 指令报错cannot import name Config from mmcvImportError: cannot import name print_log from mmcvImportError: cannot import name init_dist from mmengine.runnerWARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNon…...
C++学习内存管理
1.概念的介绍 总括: 1. 栈(Stack) 存储内容: 局部变量(包括函数参数、非静态局部变量)。 函数调用的上下文信息(如返回地址、寄存器状态等)。 特点: 内存由编译器自动…...
git使用。创建仓库,拉取分支,新建分支开发
文章目录 安装 git自己新建仓库,进行代码管理合作开发的流程拉去主分支代码查看本地分支的状态查看远程分支查看远程的仓库信息本地分支切换切换并创建分支提交代码 made by NJITZX git 是一个版本控制工具,真正开发项目中是多个人开发一个项目的&#…...
itsdangerous加解密源码分析|BUG汇总
这是我这两天的思考 早知道密码学的课就不旷那么多了 纯个人见解 如需转载,标记出处 目录 一、官网介绍 二、事例代码 源码分析: 加密函数dump源码使用的函数如下: 解密 编辑 编辑 关于签名: 为什么这个数字签名没有…...
常见限流算法及实现
1. 固定窗口计数器(Fixed Window Counter) 原理:在固定时间窗口(如1分钟)内统计请求数,超过阈值则拒绝后续请求。优点:实现简单,内存占用低。缺点:存在窗口切换时的流量…...
计算机操作系统进程(4)
系列文章目录 第二章:进程的描述与控制 文章目录 系列文章目录前言一、临界区的概念和描述:二、硬件同步机制: 1.关中断2.利用Test-and-Set指令实现互斥3.利用Swap指令实现进程的互斥 总结 前言 上一篇我们仅仅讲了一点关于线程同步的概念&a…...
编程题《牛牛的链表删除》的python可以用非链表的方式
描述 牛牛从键盘输入了一个长度为 n 的数组,把这个数组转换成链表然后把链表中所有值是 x 的节点都删除。 输入描述: 第一行输入两个正整数 n 和 x 表示数组的长度和要删除的链表节点值 x 。 第二行输入 n 个正整数表示数组中每个元素的值。 输出描述&am…...
Certbot实现SSL免费证书自动续签(CentOS 7版 + Docker部署的nginx)
前置安装,可参考Certbot实现SSL免费证书自动续签(CentOS 7 nginx/apache) 如果是通过 Docker 运行 Nginx, certbot 无法直接检测到本地的 Nginx 配置。解决方案是 使用 standalone 模式 或 挂载 Webroot 方式获取 SSL 证书&…...
C++|构造函数和析构函数
一、构造函数 构造函数是一种特殊的成员函数,主要用于创建对象时对对象进行初始化操作,即专门用于构造新对象,并赋值对象的成员数据。 在 C 里,构造函数的名称和类名相同,并且没有返回类型。当创建类的对象时&#x…...
AI日报 - 2025年3月17日
🌟 今日概览(60秒速览) ▎🤖 AGI突破 | GPT-o1在卡内基梅隆大学数学考试中获满分,展示AI数学能力新高度 成本仅5美分/题,推理速度不到1分钟 ▎💼 商业动向 | Figure推出BotQ机器人制造设施&…...
不像人做的题————十四届蓝桥杯省赛真题解析(上)A,B,C,D题解析
题目A:日期统计 思路分析: 本题的题目比较繁琐,我们采用暴力加DFS剪枝的方式去做,我们在DFS中按照8位日期的每一个位的要求进行初步剪枝找出所有的八位子串,但是还是会存在19月的情况,为此还需要在CHECK函数…...
JavaScript 中 call 和 apply 的用法与区别
文章目录 前言一、 call 方法1.1 基本用法1.2 传递多个参数 二、apply 方法2.1 基本用法2.2 传递数组参数 三、call 和 apply 的区别四、实际应用场景4.1 借用方法4.2 继承与构造函数 五、总结 前言 在 JavaScript 中,call 和 apply 是两个非常重要的函数方法&…...
go程序调用k8s pod副本的名称IP手动赋值给configmap的参数
1、创建configmap --- apiVersion: v1 data:config.yaml: >-# config.yamlEtcd:Endpoints:- "etcd-server:2379"Username: ""Password: ""Exchanges:#- Name: "Binance"# Symbol: "BTCUSDT"# WSUrl: "wss://fstr…...
面试系列|蚂蚁金服技术面【1】
哈喽,大家好!今天分享一下蚂蚁金服的 Java 后端开发岗位真实社招面经,复盘面试过程中踩过的坑,整理面试过程中提到的知识点,希望能给正在准备面试的你一些参考和启发,希望对你有帮助,愿你能够获…...
使用傅里叶变换测量声卡的频率失真
文章目录 一、说明二、关于声卡的技术详述三、实验代码获取四、结论 一、说明 假如我希望使用我的声卡来模拟软件无线电,利用声音而不是射频信号。我的声卡能胜任这项任务吗?本文将研究一种技术来找出答案。另外,需要了解音频技术的读者也可…...
Selenium 自动化测试学习总结
大概了解一下即可,现在主要用的自动化工具是 playWright,它可以录制操作。 selenium是老款自动化测试工具,仍有很多可取之处。 安装: pip install selenium即可。然后下载浏览器的驱动包,注意不是浏览器!…...
【HTML5】01-HTML摆放内容
本文介绍HTML5摆放标签的知识点。 目录 1. HTML概念 2. HTML骨架 3. 标签的关系 4. 标题标签 5. 段落标签 6. 换行和水平线 7. 文本格式化标签 8. 图像标签 图像 - 属性 9. 路径 相对路径 绝对路径 10. 超链接标签 11. 音频标签 12. 视频标签 1. HTML概念 HTM…...
内存管理:
我们今天来学习一下内存管理: 1. 内存分布: 我们先来看一下我们下面的图片: 这个就是我们的内存,我们的内存分为栈区,堆区,静态区,常量区; 我们的函数栈帧开辟消耗的内存就是我们…...
设计模式使用Java案例
代码设计要有可维护性,可复用性,可扩展性,灵活性,所有要使用设计模式进行灵活设计代码 创建型 简单工厂模式(Simple Factory) 简单工厂模式(Simple Factory Pattern)是一种创建型…...
Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南
Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现与实战指南 一、核心概念对比 1. 本质区别 维度过滤器(Filter)拦截器(Interceptor)规范层级Serv…...
